JVM④字节码技术(二)& 编译期优化


在文中,演示了方法对应字节码指令的完整过程。

下面聊一下,不同代码所对应的字节码、JVM编译期的优化处理

3、不同代码的字节码

case:整数自增

Java 整数可以自增(++ii++)和自减( --ii--

  1. iinc:int 类型的自增指令(int increament)
    • 注意直接在局部变量表的 slot 上运算,无需加载到操作数栈
    • 格式iinc slot 增量
  2. ++i 或 ++i:区别在于 iload 和 iinc 的执行顺序。
  3. 自增或自减:区别在于增量的正负号。

案例

Java 文件

public static void main(String[] args) {
    int a = 10;
    int b = a++ + ++a + a--;
    System.out.println(a);
    System.out.println(b);
}

字节码:如下,仅展示方法部分

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
    stack=2, locals=3, args_size=1
        0: bipush 10
        2: istore_1
        3: iload_1
        4: iinc 1, 1
        7: iinc 1, 1
        10: iload_1
        11: iadd
        12: iload_1
        13: iinc 1, -1
        16: iadd
        17: istore_2
        18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
        21: iload_1
        22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
        25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
        28: iload_2
        29: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
        32: return
    LineNumberTable:
        line 8: 0
        line 9: 3
        line 10: 18
        line 11: 25
        line 12: 32
    LocalVariableTable:
        Start	Length    Slot	Name	Signature
            0		33		0	args	[Ljava/lang/String;
            3		30		1	a		I
           18 		15		2	b		I

分析

主线程开始运行,分配栈帧内存

  • 操作数栈:stack = 2,容量为 2。

  • 局部变量表:locals = 3,长度为 3。

  1. bipush 10:将整数 10 入栈

  2. istore_1:将栈顶数据(int,10),保存到局部变量表下标为 1 的 slot

    image-20220301120726219
  3. iload_1:将局部变量表中 slot 1 的数据(int,10),加载到操作数栈的栈顶

  4. iinc 1, 1:将 slot 1 的整数自增 1(10 → 11)

    image-20220301121008869
  5. iinc 1, 1:将 slot 1 的整数自增 1(11 → 12)

  6. iload_1:将局部变量表中 slot 1 的数据(int,12),加载到操作数栈的栈顶

    image-20220301121206817
  7. iadd:将操作数栈的两个整数相加并出栈,求和结果(22)入栈

  8. iload_1:将局部变量表中 slot 1 的数据(int,12),加载到操作数栈的栈顶

  9. iinc 1, -1:将 slot 1 的整数自增 -1(12 → 11)

    image-20220301121828572
  10. iadd:将操作数栈的两个整数相加并出栈,求和结果(34)入栈

  11. istore_2:将栈顶数据(int,34),保存到局部变量表下标为 2 的 slot

    image-20220301122233485

到此,a 和 b 的赋值情况已分析结束。(省略打印语句的分析,可以往前一节看)

最终答案a == 11,b == 34

case:条件、循环

条件判断

public static void main(String[] args) {
    int a = 0;
    if(a == 0) {
        a = 10;
    } else {
        a = 20;	
    }
}

字节码文件:仅展示方法部分

  • ifne:判断栈顶数据是否 != 0
  • goto跳转到指定行指令
0: iconst_0		// 将整数 0 入栈
1: istore_1		// 将栈顶数据(0)保存到局部变量表 slot 1
2: iload_1		// 加载局部变量表 slot 1 数据(0)
3: ifne 12		// 判断 “!= 0”。成立则跳转到第 12 行指令,不成立则执行下一行指令
6: bipush 10	// 将整数 10 入栈
8: istore_1		// 将栈顶数据(10)保存到局部变量表 slot 1
9: goto 15		// 跳转到第 15 行指令
12: bipush 20	// 将整数 20 入栈
14: istore_1	// 将栈顶数据(20)保存到局部变量表 slot 1
15: return

循环控制

在循环控制中,使用的也是 if 语句的字节码指令。

do-while

public static void main(String[] args) {
    int a = 0;
    do {
        a++;
    } while (a < 10);
}
  • if_icmplt:判断栈顶数据两个元素,是否 < 0(top.next - top)
0: iconst_0		// 将整数 0 入栈
1: istore_1		// 将栈顶数据(0)保存到局部变量表 slot 1
2: iinc 1, 1	// 将 slot 1 的整数自增 1
5: iload_1		// 加载局部变量表 slot 1 数据(1)
6: bipush 10	// 将整数 10 入栈
8: if_icmplt 2	// 比较栈顶两个数据,判断“< 0”。成立则跳转到第 2 行指令
11:return

while 和 for

// while
public static void main(String[] args) {
    int a = 0;
    while (a < 10) {
        a++;
    }
}

// for
public static void main(String[] args){
    for (int i = 0; i < 10; i++){
    }
}

以上两种方式的代码,编译后的字节码完全相同。

  • if_icmpge:判断栈顶数据两个元素,是否 >= 0(top.next - top)
0: iconst_0		// 将整数 0 入栈
1: istore_1		// 将栈顶数据(0)保存到局部变量表 slot 1
2: iload_1		// 加载局部变量表 slot 1 数据(0)
3: bipush 10	// 将整数 10 入栈
5: if_icmpge 14 // 比较栈顶两个数据,判断“>= 0”。成立则跳转到第 14 行指令
8: iinc 1, 1	// 将 slot 1 的整数自增 1
11: goto 2		// 跳转到第 2 行指令
14: return

问题

:循环结束时 x 的值。

public static void main(String[] args) {
    int i = 0;
    int x = 0;
    while (i < 10) {
        x = x++;
        i++;
    }
}

分析:对 x = x++ 进行分析(x 初值为 0,位于 slot2)

iload_2			// 加载局部变量表 slot 2 数据(x,此时为 0)
iinc 2,1		// 将 slot 2 整数自增 1(x:0 → 1)
istore_2		// 将栈顶数据(0)存储到 slot 2(将 x 覆盖为0)

结论:每次循环中,x 先自增 1,再被覆盖为 0。

最终结果:0

case:构造方法

()V

类构造方法,针对静态结构类加载的初始化阶段被调用

public class Demo {
    static int i = 10;
    
    static { i = 20; }
    
    static { i = 30; }
}

编译器按从上往下的顺序,收集所有静态结构(静态代码块,静态成员)的赋值语句,合并为()V 方法

0: bipush 10
2: putstatic #2		// Field i:I
5: bipush 20
7: putstatic #2		// Field i:I
10: bipush 30
12: putstatic #2	// Field i:I
15: return

()V

构造方法,针对静态结构。类加载的初始化阶段被调用。

public class Demo {
    private String a = "s1";
    
    { b = 20; }
    
    private int b = 10;
    
    { a = "s2"; }
    
    public Demo3_8_2(String a, int b) {
        this.a = a;
        this.b = b;
    }
    
    public static void main(String[] args) {
        Demo3_8_2 d = new Demo3_8_2("s3", 30);
        System.out.println(d.a);
        System.out.println(d.b);
    }
}
  • 编译器按从上往下的顺序,收集所有非静态结构(代码块,成员变量)的赋值语句,合并为()V方法
  • 原始构造方法(与类同名的构造函数),也会被收集进去,但构造函数位于最后
// 父类构造方法
0: aload_0
1: invokespecial #1	// super.()V
// 非静态结构
4: aload_0
5: ldc #2			// "s1"
7: putfield #3		// this.a
10: aload_0
11: bipush 20
13: putfield #4		// this.b
16: aload_0
17: bipush 10
19: putfield #4
22: aload_0
23: ldc #5			// "s2"
25: putfield #3
// 构造函数
28: aload_0
29: aload_1
30: putfield #3
33: aload_0
34: iload_2
35: putfield #4
38: return

case:方法调用

public class Demo {
	public Demo() { }
    
    private void test1() { }
    
    private final void test2() { }
    
    public void test3() { }
    
    public static void test4() { }
    
    public static void main(String[] args) {
        Demo d = new Demo();
        
        d.test1();
        d.test2();
        d.test3();
        d.test4();
        
        Demo.test4();
    }
}
  • new:创建对象。分配堆内存,将对象引用压入操作数栈
  • dup复制操作数栈的栈顶数据,此时栈中有 2 个相同的对象引用(一个用于初始化,一个用于存储
  • 有关方法调用的说明,看本文 2.2 的字节码指令。
0: new #2
3: dup
4: invokespecial #3		// Method "":()V
7: astore_1
8: aload_1
9: invokespecial #4		// Method test1:()V
12: aload_1
13: invokespecial #5	// Method test2:()V
16: aload_1
17: invokevirtual #6	// Method test3:()V
20: aload_1
21: pop
22: invokestatic #7		// Method test4:()V
25: invokestatic #7		// Method test4:()V
28: return

case:异常

try-catch

public static void main(String[] args) {
    int i = 0;
    try {
        i = 10;
    } catch (Exception e) {
        i = 20;
    }
}

字节码

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
        stack=1, locals=3, args_size=1
            0: iconst_0
            1: istore_1
            // try
            2: bipush 10
            4: istore_1
            5: goto 12
            // catch
            8: astore_2
            9: bipush 20
            11: istore_1
            12: return
        // 异常表
		Exception table:
    		from	to	target	type
    			2	5		8	Class java/lang/Exception
		LineNumberTable: ...
    	LocalVariableTable:
            Start  Length  Slot  Name	Signature
           	9		3		2		e 	Ljava/lang/Exception;
           	0		13		0	 args 	[Ljava/lang/String;
           	2		11		1		i 	I
    	StackMapTable: ...
    MethodParameters: ...
}

异常表(Exception table),异常中特有的结构

  • from/to:可能出现异常的代码,即 try 块。【左闭右开 [from, to)】
  • target:发生匹配到的异常时的跳转行号。
  • type:异常类型

异常监测:监测 [from, to) 的代码(try 块)

  1. 发生与 catch 块匹配的异常时,跳转到 target(第 8 行)
    • a_store2:将异常对象的引用放入 slot2。
    • 执行该 catch 块的内容,结束。
  2. 没有异常,执行完 try 块代码后,跳出 try-catch 块

多个 single-catch 块

public static void main(String[] args) {
    int i = 0;
    try {
        i = 10;
    } catch (ArithmeticException e) {
        i = 30;
    } catch (NullPointerException e) {
        i = 40;
    } catch (Exception e) {
        i = 50;
    }
}

字节码

stack=1, locals=3, args_size=1
    0: iconst_0
    1: istore_1
	// try
    2: bipush 10
    4: istore_1
    5: goto 26
    // catch
    8: astore_2
    9: bipush 30
    11: istore_1
    12: goto 26
    // catch
    15: astore_2
    16: bipush 40
    18: istore_1
    19: goto 26
    // catch
    22: astore_2
    23: bipush 50
    25: istore_1
    26: return
Exception table:
    from	to	target	type
	2		5	8 		Class java/lang/ArithmeticException
	2		5	15		Class java/lang/NullPointerException
	2		5	22		Class java/lang/Exception
LineNumberTable: ...
LocalVariableTable:
    Start	Length	Slot	Name	Signature
    9		3 		2		e		Ljava/lang/ArithmeticException;
    16		3 		2		e		Ljava/lang/NullPointerException;
    23		3 		2		e		Ljava/lang/Exception;
    0		27		0		args	[Ljava/lang/String;
    2		25		1		i		I
StackMapTable: ...
MethodParameters: ...

异常表(Exception table)

  • 相比一个 single-catch 块,增加了相应个数的异常记录
  • 每个 catch 块都有一组字节码指令。

异常监测:检测 [from, to) 的代码(try 块)

  1. 发生与 catch 块匹配的异常时,跳转到 target
    1. ArithmeticException:跳转到第 8 行
    2. NullPointerException:跳转到第 15 行
    3. Exception:跳转到第 22 行
  2. astore_2:将异常对象的引用放入 slot2。
    • 由于只会匹配并进入一个异常分支,所以 slot2 公用。
  3. 执行该 catch 块的内容,结束

若没有异常,执行完 try 块代码后,跳出 try-catch 块

multi-catch 块

public static void main(String[] args) {
    try {
        Method test = Demo3_11_3.class.getMethod("test");
        test.invoke(null);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

public static void test() {
    System.out.println("ok");
}

字节码

stack=3, locals=2, args_size=1
    0: ldc #2
    2: ldc #3
    4: iconst_0
    5: anewarray #4
    8: invokevirtual #5
    11: astore_1
    12: aload_1
    13: aconst_null
    14: iconst_0
    15: anewarray #6
    18: invokevirtual #7
    21: pop
    22: goto 30
    // catch
    25: astore_1
    26: aload_1
    27: invokevirtual #11 // e.printStackTrace:()V
    30: return
Exception table:
    from	to	target	type
    0		22		25	Class java/lang/NoSuchMethodException
    0		22		25	Class java/lang/IllegalAccessException
    0		22		25	Class java/lang/reflect/InvocationTargetException
LineNumberTable: ...
LocalVariableTable:
    Start	Length	Slot	Name	Signature
    12		10		1		test	Ljava/lang/reflect/Method;
    26		4 		1		e		Ljava/lang/ReflectiveOperationException;
    0		31		0		args	[Ljava/lang/String;
StackMapTable: ...
MethodParameters: ...

异常表(Exception table)

  • 相比一个 single-catch 块,增加了相应个数的异常记录。
  • 相比多个 single-catch 块,只有一组 catch 块的字节码指令

异常监测:检测 [from, to) 的代码(try 块)

  1. 发生与 multi-catch 块匹配的异常时,跳转到 target(第 25 行)
  2. astore_1:将异常对象的引用放入 slot1。
  3. 执行该 catch 块的内容,结束。

若没有异常,执行完 try 块代码后,跳出 try-catch 块。

finally

public static void main(String[] args) {
    int i = 0;
    try {
        i = 10;
    } catch (Exception e) {
        i = 20;
    } finally {
        i = 30;
    }
}

字节码

Code:
    stack=1, locals=4, args_size=1
        0: iconst_0
        1: istore_1
		// try
        2: bipush 10
        4: istore_1
           	// finally
        5: bipush 30
        7: istore_1
        8: goto 27
        // catch Exception
        11: astore_2
        12: bipush 20
        14: istore_1
            // finally
        15: bipush 30
        17: istore_1
        18: goto 27
        // catch any
        21: astore_3
            // finally
        22: bipush 30
        24: istore_1
        25: aload_3
        26: athrow
        27: return
    Exception table:
        from	to	target	type
        2		5		11	Class java/lang/Exception
        2		5		21	any		// 剩余的异常类型,比如 Error
        11		15		21	any		// 剩余的异常类型,比如 Error
    LineNumberTable: ...
    LocalVariableTable:
        Start	Length	Slot	Name	Signature
		 12		   3	 2		e		Ljava/lang/Exception;
		  0		  28	 0		args	[Ljava/lang/String;
		  2		  26	 1		i		I
    StackMapTable: ...
MethodParameters: ...
  1. finally 块的字节码指令,被复制并分别放入 try 块、catch 块
  2. 增加 any 异常,表示可能出现的其它异常类型(如 Error)
    • catch any 异常块中,也有 finally 代码的字节码指令;
    • 该异常会被存储在一个“无名” slot,并被 athrow 指令抛出。

case:finally 对返回值影响

探讨一下,finally 块中出现 return 的情况。

:方法的返回值是什么?10 还是 20 ?

public class Demo {
    public static void main(String[] args) {
        int result = test();
    }
    public static int test() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}

在 Java SE 中,我们知道在方法返回之前会执行 finally 块,所以此时肯定返回的是 20。

从字节码的角度分析。

stack=1, locals=2, args_size=0
    // try
    0: bipush 10
    2: istore_0
        // finally
    3: bipush 20
    5: ireturn
	// catch any
    6: astore_1
        // finally
    7: bipush 20
    9: ireturn
Exception table:
    from	to	target	type
	0		3		6	any
LineNumberTable: ...
StackMapTable: ...
  1. finally 块代码对应的字节码指令,被复制并分别放入 try 块、catch any 块。
    • ireturn将栈顶的整数作为返回值返回
  2. 与上一个案例中的 finally 相比,any 异常不会被 athrow 抛出
  3. 结论当 finally 块中出现 return,异常会被吞掉

再来看 finally 块中没有 return,但修改作为返回值的变量的情况。

:方法的返回值是什么?10 还是 20 ?

public class Demo {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }
    
    public static int test() {
        int i = 10;
        try {
            return i;
        } finally {
            i = 20;
        }
    }
}

字节码

stack=1, locals=3, args_size=0
    0: bipush 10
    2: istore_0
    // try
    3: iload_0
    4: istore_1		// 将 i 放入 slot1 作为返回值
        // finally
    5: bipush 20
    7: istore_0		// 修改的是 slot0,不影响返回值
    // return
    8: iload_1		// 加载的是 slot1
    9: ireturn		// 返回栈顶数据(10)
    // catch any
    10: astore_2
        // finally
    11: bipush 20
    13: istore_0
    // 抛异常
    14: aload_2
    15: athrow
Exception table:
    from	to	target	type
    3		5		10	any
LineNumberTable: ...
LocalVariableTable:
	Start	Length	Slot	Name	Signature
		3		13	0		i		I
StackMapTable: ...
  1. finally 块代码对应的字节码指令,被复制并分别放入 try 块、catch any 块。
  2. 注意
    1. slot0 和 slot1 都存储了变量 i,分别用于值的修改和返回值。
    2. slot0:当 finally 块中修改 i 时,修改的是 slot0 的 i;
    3. slot1:当方法返回时,返回的是 slot1 的i。
    4. 说明:i 值不会被修改,相当于锁定了返回值
  3. 结论当 finally 块中没有 return,try 块中固定了返回值,不会被 finally 影响!

case:同步

public static void main(String[] args) {
    Object lock = new Object();
    
    synchronized (lock) {
        System.out.println("ok");
    }
}

字节码

stack=2, locals=4, args_size=1
    // 实例化对象并初始化
    0: new #2
    3: dup
    4: invokespecial #1
    7: astore_1
    // 开始同步代码块
    8: aload_1
    9: dup
    10: astore_2			// 复制一份lock引用,用于解锁
        // 加锁
    11: monitorenter
    12: getstatic #3		// <- System.out
    15: ldc #4				// <- "ok"
    17: invokevirtual #5	// invokevirtual println:(Ljava/lang/String;)V
        // 解锁
    20: aload_2
    21: monitorexit
    22: goto 30
	// catch any
    25: astore_3
        // 解锁
    26: aload_2
    27: monitorexit
    // 抛异常
    28: aload_3
    29: athrow
    30: return
Exception table:
    from	to	target	type
    12		22		25	any
    25		28		25	any
LineNumberTable: ...
LocalVariableTable:
    Start	Length	Slot	Name	Signature
    	0		31	0		args	[Ljava/lang/String;
    	8		23	1		lock	Ljava/lang/Object;
StackMapTable: ...
  1. 进入同步代码块后,会复制一份被加锁的对象并存储在局部变量表,用于解锁
  2. monitorenter 加锁,monitorexit 解锁。加锁和解锁指令成对出现
  3. 不论是否会出现异常,monitor 都会将对象引用解锁。
  4. 方法级别的 synchronized 不会在字节码指令中有所体现

4、编译期优化

Java 编译器,在编译期会自动生成和转换一些代码。

case:默认构造

public class Demo1 {
}

编译后相当于

public class Demo1 {
    // 默认构造
    public Candy1() {
        super(); // 调用父类 Object 的无参构造
        ":()V
    }
}

case:自动拆装箱

JDK 5 引入自动拆装箱

// 源代码1
public class Demo2 {
    public static void main(String[] args) {
        Integer x = 1;
        int y = x;
    }
}
  • < JDK 5:无法通过编译,必须修改为以下代码
  • >= JDK 5:编译期自动转换
// 源代码2
public class Demo2 {
    public static void main(String[] args){
        Integer x = Integer.valueOf(1);
        int y = x.intValue();
    }
}

case:泛型

泛型擦除

JDK 5 引入泛型,编译为字节码后会将泛型擦除

泛型信息在编译为字节码后就丢失了,实际的类型都会当作 Object 类型来处理。

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

在取值时,编译器会在字节码中做一个类型转换的操作。

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

若变量 x 是基本类型,则相当于如下操作:

// 先将Object转为Integer,再拆箱
int x = ((Integer)list.get(0)).intValue();

LocalVariableTypeTable

泛型擦除,将字节码上的泛型信息擦除了。

但是本地变量类型表仍保留了方法参数泛型的信息,可以通过反射获取

stack=2, locals=3, args_size=1
    0: new #2					// class java/util/ArrayList
    3: dup
    4: invokespecial #3 		// Method java/util/ArrayList."
    ":()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;

反射获取

定义一个方法:方法返回值、参数列表都是泛型集合。

public Set test(List list, Map map) {
}

反射代码

// 获取指定参数类型的方法
Method test = Demo3.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

case:可变参数

JDK 5 引入。

public class Demo4 {
    public static void foo(String... args) {
        String[] array = args;
        System.out.println(array);
    }
    public static void main(String[] args) {
        foo("hello", "world");
    }
}

可变参数...,实际上就是一维数组

编译后相当于

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

:如果调用方法时没有传入任何参数,则相当于创建并传递了一个空数组,而不是传 null

// 源代码
public static void main(String[] args) {
    foo();
}
// 编译后相当于
public static void main(String[] args) {
    foo(new String[]{});
}

case:foreach 循环

数组

JDK 5 引入。

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

数组 foreach 被转换为 for 循环。

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

集合 foreach 被转换为迭代器循环

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

case:枚举类

JDK 7 引入了枚举类。

enum Gender {
    MALE, FEMALE
}

枚举类被转换为一个继承了 Enum 的最终类。(单例 + 工厂)

  • name:枚举常量名称
  • ordinary:序号,从 0 开始
public final class Gender extends Enum {
    public static final Gender MALE;
    public static final Gender FEMALE;
    private static final Gender[] $VALUES;
    static {
        MALE = new Gender("MALE", 0);
        FEMALE = new Gender("FEMALE", 1);
        $VALUES = new Gender[]{MALE, FEMALE};
    }

    private Gender(String name, int ordinal) {
        super(name, ordinal);
    }
    public static Gender[] values() {
        return $VALUES.clone();
    }
    public static Gender valueOf(String name) {
        return Enum.valueOf(Gender.class, name);
    }
}

case:switch

JDK 7 开始,switch 可以搭配字符串和枚举类

switch 字符串

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

switch 字符串,被转换为两个搭配整数的 switch-case。

  1. 获取字符串的 hashCode,比较 hashCode 和 equals()
  2. 根据第一个 switch-case 的结果,执行 case 块中的代码
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 枚举

enum Gender {
    MALE, FEMALE
}

public class Demo7 {
    public static void foo(Gender gender) {
        switch (gender) {
            case MALE:
                System.out.println("男"); break;
            case FEMALE:
                System.out.println("女"); break;
        }
    }
}

编译后相当于

  • 创建一个合成类,映射枚举的 ordinary 与 数组元素
  • switch 字符串,被转换为 switch ordinary。
public class Candy7 {
    static class $MAP {
        // 数组大小即为枚举元素个数,里面存储case用来对比的数字
        static int[] map = new int[2];
        static {
            // 枚举的 ordinal 从 0 开始
            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;
        }
    }
}

case:try-with-resources

JDK 7 新增,资源对象需实现 AutoCloseable 接口

可以不用写 finally 块,编译器会自动释放资源的代码。

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

常见的有 InputStream、OutputStream、Connection、Statement、ResultSet 等接口。

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

编译后相当于在 try 块中创建资源对象,并再次使用 try-catch 语句释放资源

分析 try 块

  1. 创建资源对象
  2. 在 try 内部再使用 try-catch 语句执行外部 try 块代码,若捕获异常则抛出。
  3. 若无异常,在 finally 中判断资源是否为空,非空则需要释放
  4. 判断内部 try 块中是否发生异常
    • 没有则直接关闭资源。
    • 有,则再使用一次 try-catch 语句执行资源释放操作。
      • 若在资源释放过程中,再次发生异常,则作为压制异常添加。
      • 目的不覆盖掉 try 块中的异常,保留所有可能出现的异常
public class Demo9 {
    public Demo9() {
    }
    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 = e1;
                throw e1;
            } finally {
                // 资源非空,需要释放
                if (is != null) {
                    // 内部try块发生异常
                    if (t != null) {
                        try {
                            is.close();
                        } catch (Throwable e2) {
                            // 资源释放出现异常,作为被压制异常添加
                            t.addSuppressed(e2);
                        }
                    } else {
                        is.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

case:方法重写桥接

子类可以重写父类的方法。

重写方法的返回值,必须是与父类返回值类型相同,或父类返回值的子类型

class A {
    public Number m() {
        return 1;
    }
}

class B extends A {
    @Override
    public Integer m() {
        return 2;
    }
}

Class B 编译后相当于生成一个桥接方法,作为真正的重写方法。

桥接方法调用子类中声明的方法

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

case:匿名内部类

定义匿名内部类

public class Demo11 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
    }
}

编译后相当于生成一个额外类,通过构造器创建。

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

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

定义匿名内部类,且引用局部变量

内部类引用了方法参数 x。

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

编译后相当于生成一个额外类,将局部变量作为构造器参数传入。

public class Candy11 {
    public static void test(final int x) {
        Runnable runnable = new Candy11$1(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);
    }
}

在 Java SE 中,当匿名内部类引用局部变量时,局部变量必须是 final 的

原因:局部变量作为构造器参数传入额外类,并且额外类生成后就不会再改变,因此局部变量也不允许改变

JVM