JVM-编译期处理与Java语法糖

news2024/10/24 4:33:14

JVM-编译期处理

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃嘛)

注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记

默认构造器
public class Candy1 {
}

编译成class后的代码

public class Candy1 {
    // 这个无参构造是编译器帮助我们加上的
    public Candy1() {
        super(); // 即调用父类 Object 的无参构造方法,即调用 java/lang/Object."
        <init>":()V
    }
}
自动拆装箱

这个特性是 JDK 5 开始加入的, 代码片段1

public class Candy2 {
    public static void main(String[] args) {
        Integer x = 1;
        int y = x;
    }
}

这段代码在 JDK 5 之前是无法编译通过的,必须改写为 代码片段2

public class Candy2 {
    public static void main(String[] args) {
        Integer x = Integer.valueOf(1);
        int y = x.intValue();
    }
}

显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转换的事情在 JDK 5 以后都由编译器在编译阶段完成。即 代码片段1 都会在编译阶段被转换为 代码片段2

泛型集合取值

泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理

public class Candy3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(10); // 实际调用的是 List.add(Object e)
        Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index);
    }
}

所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作

// 需要将 Object 转为 Integer
Integer x = (Integer)list.get(0);

如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是

// 需要将 Object 转为 Integer, 并执行拆箱操作
int x = ((Integer)list.get(0)).intValue();

还好这些麻烦事都不用自己做

擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法参数泛型的信息

public cn.itcast.jvm.t3.candy.Candy3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=1, locals=1, args_size=1
    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."
    <init>":()V
    4: return
    LineNumberTable:
    line 6: 0
    LocalVariableTable:
    Start Length Slot Name Signature
    0 5 0 this Lcn/itcast/jvm/t3/candy/Candy3;
    public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
    stack=2, locals=3, args_size=1
    0: new #2 // class java/util/ArrayList
    3: dup
    4: invokespecial #3 // Method java/util/ArrayList."
    <init>":()V
    7: astore_1
    8: aload_1
    9: bipush 10
    11: invokestatic #4 // Method
    java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    14: invokeinterface #5, 2 // InterfaceMethod
    java/util/List.add:(Ljava/lang/Object;)Z
    19: pop
    20: aload_1
    21: iconst_0
    22: invokeinterface #6, 2 // InterfaceMethod
    java/util/List.get:(I)Ljava/lang/Object;
    27: checkcast #7 // class java/lang/Integer
    30: astore_2
    31: return
    LineNumberTable:
    line 8: 0
    line 9: 8
    line 10: 20
    line 11: 31
    LocalVariableTable:
    Start Length Slot Name Signature
    0 32 0 args [Ljava/lang/String;
    8 24 1 list Ljava/util/List;
    LocalVariableTypeTable:
    Start Length Slot Name Signature
    8 24 1 list Ljava/util/List<Ljava/lang/Integer;>;

使用反射,仍然能够获得这些信息

public Set<Integer> test(List<String> list, Map<Integer, Object> map) {
}
Method test = Candy3.class.getMethod("test", List.class, Map.class);
Type[] types = test.getGenericParameterTypes();
for (Type type : types) {
    if (type instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) type;
        System.out.println("原始类型 - " + parameterizedType.getRawType());
        Type[] arguments = parameterizedType.getActualTypeArguments();
        for (int i = 0; i < arguments.length; i++) {
            System.out.printf("泛型参数[%d] - %s\n", i, arguments[i]);
        }
    }
}
原始类型 - interface java.util.List
泛型参数[0] - class java.lang.String
原始类型 - interface java.util.Map
泛型参数[0] - class java.lang.Integer
泛型参数[1] - class java.lang.Object
可变参数

可变参数也是 JDK 5 开始加入的新特性

public class Candy4 {
    public static void foo(String... args) {
        String[] array = args; // 直接赋值
        System.out.println(array);
    }
    public static void main(String[] args) {
    	foo("hello", "world");
    }
}

可变参数 String… args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。同样 java 编译器会在编译期间将上述代码变换为

public class Candy4 {
    public static void foo(String[] args) {
        String[] array = args; // 直接赋值
        System.out.println(array);
    }
    public static void main(String[] args) {
    	foo(new String[]{"hello", "world"});
    }
}

注意

如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递null 进去

foreach循环

仍是 JDK 5 开始引入的语法糖,数组的循环

public class Candy5_1 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5}; // 数组赋初值的简化写法也是语法糖哦
        for (int e : array) {
        	System.out.println(e);
        }
    }
}

会被编译器转换为

public class Candy5_1 {
    public Candy5_1() {
    }
    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5};
        for(int i = 0; i < array.length; ++i) {
        int e = array[i];
        System.out.println(e);
        }
    }
}

而集合的循环

public class Candy5_2 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        for (Integer i : list) {
        System.out.println(i);
        }
    }
}

实际被编译器转换为对迭代器的调用

public class Candy5_2 {
    public Candy5_2() {
    }
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Iterator iter = list.iterator();
        while(iter.hasNext()) {
        Integer e = (Integer)iter.next();
        System.out.println(e);
        }
    }
}

注意

foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中Iterable 用来获取集合的迭代器( Iterator )

switch 字符串

从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖

public class Candy6_1 {
    public static void choose(String str) {
        switch (str) {
            case "hello": {
            System.out.println("h");
            break;
        }
        	case "world": {
            System.out.println("w");
            break;
            }
        }
    }
}

注意

switch 配合 String 和枚举使用时,变量不能为null,原因分析完语法糖转换后的代码应当自然清楚

会被编译器转换为

public class Candy6_1 {
    public Candy6_1() {
    }
    public static void choose(String str) {
        byte x = -1;
        switch(str.hashCode()) {
        case 99162322: // hello 的 hashCode
            if (str.equals("hello")) {
                x = 0;
            }
        break;
        case 113318802: // world 的 hashCode
            if (str.equals("world")) {
            x = 1;
        }
        }
        switch(x) {
            case 0:
            System.out.println("h");
            break;
        case 1:
            System.out.println("w");
        }
       }
}

可以看到,执行了两遍 switch,第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应byte 类型,第二遍才是利用 byte 执行进行比较。为什么第一遍时必须既比较 hashCode,又利用 equals 比较呢?hashCode 是为了提高效率,减少可能的比较;而 equals 是为了防止 hashCode 冲突,例如 BM 和 C. 这两个字符串的hashCode值都是2123 ,如果有如下代码:

public class Candy6_2 {
    public static void choose(String str) {
        switch (str) {
            case "BM": {
            System.out.println("h");
            break;
            }
        	case "C.": {
            System.out.println("w");
            break;
            }
        }
    }
}

会被编译器转换为:

public class Candy6_2 {
    public Candy6_2() {
    }
    public static void choose(String str) {
        byte x = -1;
        switch(str.hashCode()) {
            case 2123: // hashCode 值可能相同,需要进一步用 equals 比较
            if (str.equals("C.")) {
            x = 1;
        } else if (str.equals("BM")) {
        	x = 0;
    	}
    default:
    switch(x) {
        case 0:
            System.out.println("h");
            break;
        case 1:
        	System.out.println("w");
        }
    	}
    }
}
switch 枚举

switch 枚举的例子,原始代码

enum Sex {
	MALE, FEMALE
}
public class Candy7 {
    public static void foo(Sex sex) {
        switch (sex) {
        	case MALE:
        	System.out.println("男"); break;
            case FEMALE:
            System.out.println("女"); break;
        }
    }
}

转换后代码

public class Candy7 {
    /**
    * 定义一个合成类(仅 jvm 使用,对我们不可见)
    * 用来映射枚举的 ordinal 与数组元素的关系
    * 枚举的 ordinal 表示枚举对象的序号,从 0 开始
    * 即 MALE 的 ordinal()=0,FEMALE 的 ordinal()=1
    */
    static class $MAP {
        // 数组大小即为枚举元素个数,里面存储case用来对比的数字
        static int[] map = new int[2];
        static {
        map[Sex.MALE.ordinal()] = 1;
        map[Sex.FEMALE.ordinal()] = 2;
    }
    }
    public static void foo(Sex sex) {
        int x = $MAP.map[sex.ordinal()];
        switch (x) {
        case 1:
        	System.out.println("男");
        	break;
        case 2:
             System.out.println("女");
    		break;
    	}
    }
}
枚举类

JDK 7 新增了枚举类,以前面的性别枚举为例

enum Sex {
    MALE, FEMALE
}

转换后代码

public final class Sex extends Enum<Sex> {
    public static final Sex MALE;
    public static final Sex FEMALE;
    private static final Sex[] $VALUES;
    static {
        MALE = new Sex("MALE", 0);
        FEMALE = new Sex("FEMALE", 1);
        $VALUES = new Sex[]{MALE, FEMALE};
    }
    /**
    * Sole constructor. Programmers cannot invoke this constructor.
    * It is for use by code emitted by the compiler in response to
    * enum type declarations.
    *
    * @param name - The name of this enum constant, which is the identifier
    * used to declare it.
    * @param ordinal - The ordinal of this enumeration constant (its position
    * in the enum declaration, where the initial constant is
    assigned
    */
    private Sex(String name, int ordinal) {
    	super(name, ordinal);
    }
    public static Sex[] values() {
    	return $VALUES.clone();
    }
    public static Sex valueOf(String name) {
    	return Enum.valueOf(Sex.class, name);
    }
}
try-with-resources

JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources

try(资源变量 = 创建资源对象){
} catch( ) {
}

其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-with-resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如

public class Candy9 {
    public static void main(String[] args) {
    try(InputStream is = new FileInputStream("d:\\1.txt")) {
    	System.out.println(is);
    } catch (IOException e) {
    	e.printStackTrace();
    }
    }
}

会被转换为

public class Candy9 {
    public Candy9() {
    }
    public static void main(String[] args) {
    try {
        InputStream is = new FileInputStream("d:\\1.txt");
        Throwable t = null;
        try {
        	System.out.println(is);
        } catch (Throwable e1) {
            // t 是我们代码出现的异常
            t = e1;
        	throw e1;
        } finally {
            // 判断了资源不为空
            if (is != null) {
                // 如果我们代码有异常
                if (t != null) {
                try {
                	is.close();
                } catch (Throwable e2) {
                    // 如果 close 出现异常,作为被压制异常添加
                    t.addSuppressed(e2);
                }
                } else {
                // 如果我们代码没有异常,close 出现的异常就是最后 catch 块中的 e
                is.close();
            	}
            }
    	}
       } catch (IOException e) {
		e.printStackTrace();
		}
	}
}

为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常)

public class Test6 {
    public static void main(String[] args) {
    try (MyResource resource = new MyResource()) {
    	int i = 1/0;
    } catch (Exception e) {
    	e.printStackTrace();
    	}
    }
    }
    class MyResource implements AutoCloseable {
        public void close() throws Exception {
            throw new Exception("close 异常");
    }
}
java.lang.ArithmeticException: / by zero
at test.Test6.main(Test6.java:7)
Suppressed: java.lang.Exception: close 异常
at test.MyResource.close(Test6.java:18)
at test.Test6.main(Test6.java:6)

如以上代码所示,两个异常信息都不会丢

方法重写时的桥接方法

我们都知道,方法重写时对返回值分两种情况:父子类的返回值完全一致子类返回值可以是父类返回值的子类(比较绕口,见下面的例子)

class A {
    public Number m() {
    return 1;
    }
}
class B extends A {
    @Override
    // 子类 m 方法的返回值是 Integer 是父类 m 方法返回值 Number 的子类
    public Integer m() {
    	return 2;
    }
}

对于子类,java 编译器会做如下处理

class B extends A {
    public Integer m() {
    	return 2;
	}	
// 此方法才是真正重写了父类 public Number m() 方法
	public synthetic bridge Number m() {
// 调用 public Integer m()
		return m();	
	}
}

其中桥接方法比较特殊,仅对 java 虚拟机可见,并且与原来的 public Integer m() 没有命名冲突,可以用下面反射代码来验证

for (Method m : B.class.getDeclaredMethods()) {
	System.out.println(m);
}
public java.lang.Integer test.candy.B.m()
public java.lang.Number test.candy.B.m()
匿名内部类
public class Candy11 {
public static void main(String[] args) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("ok");
        }
    };
    }
}

转换后代码

// 额外生成的类
final class Candy11$1 implements Runnable {
    Candy11$1() {
    }
	public void run() {
		System.out.println("ok");
	}
}

public class Candy11 {
    public static void main(String[] args) {
    	Runnable runnable = new Candy11$1();
    }
}

引用局部变量的匿名内部类,源代码

public class Candy11 {
    public static void test(final int x) {
        Runnable runnable = new Runnable() {
        @Override
        public void run() {
        	System.out.println("ok:" + x);
        }
        };
    }
}

转换后的代码

// 额外生成的类
final class Candy11$1 implements Runnable {
    int val$x;
    Candy11$1(int x) {
    this.val$x = x;
}
public void run() {
    System.out.println("ok:" + this.val$x);
    }
}
public class Candy11 {
    public static void test(final int x) {
    	Runnable runnable = new Candy11$1(x);
    }
}

注意

这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建Candy11$1 对象时,将 x 的值赋值给了 Candy11 1 对象的 v a l 1 对象的 val 1对象的valx 属性,所以 x 不应该再发生变化了,如果变化,那么 val$x 属性没有机会再跟着一起变化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【毕业设计】基于SpringBoot的网上商城系统

前言 &#x1f525;本系统可以选作为毕业设计&#xff0c;运用了现在主流的SSM框架&#xff0c;采用Maven来帮助我们管理依赖&#xff0c;所选结构非常合适大学生所学的技术&#xff0c;非常合适作为大学的毕业设计&#xff0c;难以适中。 &#x1f525;采用技术&#xff1a;Sp…

Java项目-基于springboot框架的疫苗接种管理系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

huggingface的数据集下载(linux下clone)

1. 安装lfs sudo apt-get install git-lfs 或者 apt-get install git-lfs 2. git lfs install git lfs install 3. git clone dataset包 第2&#xff0c;3步骤的截图如下&#xff1a;

CentOS7 上安装GitLab的经历

一、安装必要的基础环境 1.安装依赖包 [rootgitlab-server ~]#yum install curl policycoreutils openssh-server openssh-clients postfix wget git patch -y [rootgitlab-server ~]# systemctl start postfix 2.配置yum源(由于网络问题&#xff0c;国内用户请使用清华大学…

架构设计笔记-21-案例分析

1.遗留系统策略 / 数据迁移 / REST和RPC风格 2.分布式系统 / 分布式对象调用 3.开放式架构 / GOA 4.ESB 5.FMEA故障分析 6. 加密 / 公钥体系机制 / 加解密API和透明加密 7.嵌入式系统故障 / 故障滤波算法 / 容错算法 8.开源框架struts / spring / Hibenate 9.企业应用集成 10.T…

Python 应用可观测重磅上线:解决 LLM 应用落地的“最后一公里”问题

作者&#xff1a;彦鸿 背景 随着 LLM&#xff08;大语言模型&#xff09;技术的不断成熟和应用场景的不断拓展&#xff0c;越来越多的企业开始将 LLM 技术纳入自己的产品和服务中。LLM 在自然语言处理方面表现出令人印象深刻的能力。然而&#xff0c;其内部机制仍然不明确&am…

2023 WMCTF pwn【blindless jit】

文章目录 blindlessIDA结构体命名逆向漏洞方法1方法2 exp jitstrtol(v9, &endptr, 16)__errno_location和__throw_out_of_range详细解释&#xff1a; __errno_location相关具体操作详细分析为什么要执行上述代码&#xff1f;示例代码段的解释 _acrt_iob_funcSetProcessMiti…

Vue.js 学习总结(9)—— Vue 3 组件封装技巧

1、需求说明 需求背景&#xff1a;日常开发中&#xff0c;我们经常会使用一些UI组件库诸如and design vue、element plus等辅助开发&#xff0c;提升效率。有时我们需要进行个性化封装&#xff0c;以满足在项目中大量使用的需求。错误示范&#xff1a;基于a-modal封装一个自定…

MinIO安装教程

MinIO简介 Minio是一个开源的、云原生的分布式对象存储系统&#xff0c;是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据。 它一大特点就是轻量&#xff0c;虽然轻量&#xff0c;却拥有着不…

适合忙碌职场人的进度计划工具

明确的进度计划是一种约束机制&#xff0c;职场人往往面临多项任务&#xff0c;通过进度计划管理&#xff0c;可以将工作按照优先级和时间要求进行分解&#xff0c;而进度计划管理可以很好地帮助职场人发现时间浪费的环节。此外&#xff0c;对于忙碌的职场人来说&#xff0c;不…

基于K8S的StatefulSet部署mysql主从

StatefulSet特性 StatefulSet的网络状态 拓扑状态&#xff1a;应用的多个实例必须按照某种顺序启动&#xff0c;并且必须成组存在&#xff0c;例如一个应用中必须存在一个A Pod和两个B Pod&#xff0c;且A Pod必须先于B Pod启动的场景 存储状态&#xff1a;应用存在多个实例&…

《使用Gin框架构建分布式应用》阅读笔记:p88-p100

《用Gin框架构建分布式应用》学习第6天&#xff0c;p88-p100总结&#xff0c;总计13页。 一、技术总结 1.MongoDB CRUD操作 (1)InsertOne(), InsertMany() (2)Find() (3)UpdateOne, UpdateMany() (4)DeleteOne(), DeleteMany() 2.MongoDB primitive p96&#xff0c;rec…

Docker 基础入门

Docker 基础入门 前言 在云计算和微服务架构日益盛行的今天&#xff0c;软件开发与部署的效率和灵活性成为了企业竞争力的关键因素之一。Docker&#xff0c;作为一种开源的容器化平台&#xff0c;凭借其轻量级、可移植性和易于管理的特性&#xff0c;迅速成为现代软件开发和运…

pdf编辑软件有哪些?方便好用的pdf编辑软件分享

PDF文件因其跨平台、格式固定的特性&#xff0c;成为了工作、学习和生活中不可或缺的一部分。然而&#xff0c;随着需求的不断增加&#xff0c;仅仅阅读PDF文件已难以满足我们的需求&#xff0c;编辑、转换PDF文件成为了新的焦点&#xff0c;下面给大家分享几款方便好用的PDF编…

vue3处理货名的拼接

摘要&#xff1a; 货品的拼接规则是&#xff1a;【品牌】货名称/假如货品名称为空时&#xff0c;直接选择品牌为【品牌】赋值给货品&#xff0c;再选择品牌&#xff0c;会替换【品牌】&#xff1b;假如货名称为【品牌】名称&#xff0c;再选择品牌只会替换【品牌】&#xff0c;…

Windows系统PyCharm右键运行.sh文件

在参考了Windows系统下pycharm运行.sh文件&#xff0c;执行shell命令_shell在pycharm-CSDN博客 和深度学习&#xff1a;PyCharm中运行Bash脚本_pycharm bash-CSDN博客 配置了右键执行.sh文件之后&#xff0c;发现在Windows的PyCharm中直接右键运行sh文件&#xff0c;存在如下…

【算法】深入理解布隆过滤器

1. 什么是布隆过滤器&#xff1f; 布隆过滤器&#xff08;Bloom Filter&#xff09;是一种空间效率极高的概率型数据结构&#xff0c;用于检测某个元素是否在一个集合中。与常见的数据结构如哈希表不同&#xff0c;布隆过滤器无法删除元素&#xff0c;并且会存在一定的误判率&…

【重学 MySQL】六十七、解锁检查约束,守护数据完整性

【重学 MySQL】六十七、解锁检查约束&#xff0c;守护数据完整性 检查约束的基本概念检查约束的语法检查约束的使用场景注意事项示例 在MySQL中&#xff0c;检查约束&#xff08;CHECK&#xff09;是一种用于确保表中数据满足特定条件的约束。 检查约束的基本概念 检查约束用…

【Next.js 项目实战系列】05-删除 Issue

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c;给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 项目实战系列】04-修改 Issue 删除 Issue 添加删除 Button​ 本节代码链接 这里我们主要关注布局…

IPC 管道 Linux环境

管道通信的特点&#xff1a; 1. 单工通信---- 任何一个时刻只能发送方 向 接收方发送数据 2. 流式传输&#xff1a; 1> 先发送的数据先被接收&#xff0c;不能跳跃式接收 ----- 顺序发送顺序接收 2> 未被接收的数据仍然滞留在管道中&#xff0c;下一次可以继续接收后…