【JVM】- 类加载与字节码结构2

编译期处理(语法糖)

java编译器把.java源码编译成.class字节码的过程,自动生成和转换的一些代码。

默认构造器

java 复制代码
public class Candy01 {
}

编译成class后的代码

java 复制代码
public class Candy1 {
	public Candy1(){
		super();
	}
}

自动拆装箱(jdk5加入)

java 复制代码
public class Candy02 {
    public static void main(String[] args) {
        /**
         * jdk5之前:
         * Integer x = Integer.valueOf(1);
         * int y = x.intValue();
         */
        Integer x = 1; // 自动拆箱
        int y = x; // 自动装箱
    }
}

泛型集合取值

java 复制代码
public class Candy03 {
    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 idx);
    }
}

编译器在获取真正字节码时,需要额外做一个类型转换的操作:

java 复制代码
Integer x = (Integer)list.get(0);

可变参数

java 复制代码
public class Candy04 {
    public static void foo(String... args) {
        String[] array = args;
        System.out.println(array);
    }
    public static void main(String[] args) {
        foo("hello", "world");
    }
}

编译后的代码:

java 复制代码
public class Candy04 {
    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循环

java 复制代码
public class Candy05_01 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5};
        for(int e : array) {
            System.out.println(e);
        }
    }
}

会被编译为:

java 复制代码
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);
}

List - foreach循环

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

会被编译成:

java 复制代码
Iterator it = list.iterator();
while(it.hasNext()) {
    Integer e = (Integer) it.next();
    System.out.println(e);
}

foreach循环写法可以配合数组,以及所有实现了Iterator接口的集合类一起使用

switch - 字符串

java 复制代码
public class Candy06_01 {
    public static void choose(String str) {
        switch(str) {
            case "hello": {
                System.out.println("h");
                break;
            }
            case "world": {
                System.out.println("w");
                break;
            }
        }
    }
}

会被编译成:

java 复制代码
public class Candy06_01 {
    public static void choose(String str) {
        byte x = -1;
        switch(str.hashCode()) {
            case 99162322:  // "hello"的hash值
                if (str.equals("hello")) {
                    x = 0;
                }
                break;
            case 113318802:  // "world"的hash值
                if (str.equals("world")) {
                    x = 1;
                }
        }

        switch(x) {
            case 0:
                System.out.println("h");
                break;
            case 1:
                System.out.println("w");
        }
    }
}

编译后其实是执行了两次switch

  • 第一次是根据hash和equals将字符串转换为相应的byte类型
  • 第二次才是利用byte进行比较。

第一遍使用hashCode进行比较,是为了提高效率,较少可能的比较;而equals是为了防止hash冲突。

switch - 枚举类

java 复制代码
enum Sex {
    MALE, FEMALE
}
public class Candy06_02 {
    public static void foo(Sex sex) {
        switch(sex) {
            case MALE: {
                System.out.println("男");
                break;
            }
            case FEMALE: {
                System.out.println("女");
                break;
            }
        }
    }
}

编译后代码:

java 复制代码
public class Candy06_02 {
    public static void foo(Sex sex) {
        // 获取枚举的序号(MALE.ordinal() = 0, FEMALE.ordinal() = 1)
        int ordinal = sex.ordinal(); 
        switch (ordinal) {
            case 0:  // MALE
                System.out.println("男");
                break;
            case 1:  // FEMALE
                System.out.println("女");
                break;
        }
    }
}

枚举类

java 复制代码
enum Sex {
    MALE, FEMALE
}

转换后代码:

java 复制代码
public final class Sex extends java.lang.Enum<Sex> {
    // 枚举常量(public static final)
    public static final Sex MALE;
    public static final Sex FEMALE;

    // 私有构造函数(enum 不能外部实例化)
    private Sex(String name, int ordinal) {
        super(name, ordinal);
    }

    // 静态初始化块(初始化所有枚举值)
    static {
        MALE = new Sex("MALE", 0);
        FEMALE = new Sex("FEMALE", 1);
        $VALUES = new Sex[]{MALE, FEMALE};
    }

    // 自动生成的方法:values() 返回所有枚举值
    public static Sex[] values() {
        return (Sex[])$VALUES.clone();
    }

    // 自动生成的方法:valueOf(String) 根据名字返回枚举
    public static Sex valueOf(String name) {
        return (Sex)Enum.valueOf(Sex.class, name);
    }

    // 内部存储所有枚举值的数组
    private static final Sex[] $VALUES;
}

try-with-resources简化资源关闭

java 复制代码
public class Candy07 {
    public static void main(String[] args) {
        /*
            try(资源变量 = 创建资源对象) {
            
            }catch() {
            
            }
         */
        try(InputStream is = new FileInputStream("d:\\test.txt")) {
            System.out.println(is);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

其中:资源对象需要实现AutoCloseable接口,例如:InputStreamOutPutStreamConnectionStatementResultSet等接口都实现了AutoCloseable接口,使用try-with-resources可以不用写finally语句,编译器会帮助我们生成资源关闭的代码。

上边代码被编译为:

java 复制代码
public class Candy07 {
    public static void main(String[] args) {
        try {
            InputStream is = new FileInputStream("d:\\test.txt");
            Throwable t = null;
            try {
                System.out.println(is);
            }catch (Throwable e1){
                t = e1; // t是我们代码中出现的异常
                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();
        }
    }
}

如果我们的代码出现了异常,并且关闭资源的时候又出现了异常,如果想让这两个异常都显示,可以使用压制异常

例如:

java 复制代码
public class Candy08 {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            int i = 1 / 0; // 除0异常
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw new Exception("close异常"); // 资源关闭异常
    }
}

输出: 两个异常都会被抛出

方法重写时的桥接方法

java 复制代码
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(),没有命名冲突。

无参的匿名内部类

java 复制代码
public class Candy08 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
    }
}

编译后代码:

java 复制代码
// 额外生成的类
final class Candy08$1 implements Runnable {
    Candy08$1() {}
    @Override
    public void run() {
        System.out.println("ok");
    }
}
public class Candy08 {
    public static void main(String[] args) {
        Runnable runnable = new Candy08$1();
    }
}

引用局部变量的匿名内部类

java 复制代码
public class Candy10 {
    public static void test(final int x) {
        Runnable runnable = new Runnable() {
            public void run() {
                System.out.println(x);
            }
        };
    }
}

编译后:

java 复制代码
// 额外生成的类
final class Candy10$1 implements Runnable {
    int val$x;
    Candy10$1(int x) {
        this.val$x = x;
    }
    public void run() {
        System.out.println(this.val$x);
    }

}
public class Candy10 {
    public static void test(final int x) {
        Runnable runnable = new Candy10$1(x);
    }
}

这也解释了为什么匿名内部类里边使用的变量必须使用final修饰:因为在创建Candy10$1对象时,将x的值赋值给了Candy10$1对象的val$x属性,所以x的值不会在发生变化了。如果发生变化,那么val$x属性没有机会再跟着一起变化了。

相关推荐
旷世奇才李先生8 分钟前
Eclipse 安装使用教程
java·ide·eclipse
考虑考虑24 分钟前
解决java: java.lang.ExceptionInInitializerError com.sun.tools.javac.code.TypeTag :
java·spring boot·后端
Rebecca.Yan28 分钟前
Maven下载与配置&&对Java项目的理解
java·开发语言·maven
虾条_花吹雪41 分钟前
Spring for Apache Pulsar->Reactive Support->Message Consumption
java·ai·spring pulsar
慕木兮人可1 小时前
idea安装maven 拉取依赖失败的解决办法
java·maven·intellij-idea
魔镜魔镜_谁是世界上最漂亮的小仙女1 小时前
Spring-AOP
java·后端
东阳马生架构1 小时前
订单初版—6.生单链路实现的重构文档
java
MuYiLuck1 小时前
【JVM|类加载】第三天
java·jvm
Pocker_Spades_A1 小时前
飞算JavaAI:开启 Java 开发 “人机协作” 新纪元
java·开发语言·飞算javaai