JVM④字节码技术(二)& 编译期优化
在文中,演示了方法对应字节码指令的完整过程。
下面聊一下,不同代码所对应的字节码、JVM编译期的优化处理。
3、不同代码的字节码
case:整数自增
Java 整数可以自增(++i 和 i++)和自减( --i 和 i--)
- iinc:int 类型的自增指令(int increament)
- 注意:直接在局部变量表的 slot 上运算,无需加载到操作数栈。
- 格式:
iinc slot 增量。
- ++i 或 ++i:区别在于 iload 和 iinc 的执行顺序。
- 自增或自减:区别在于增量的正负号。
案例
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。
-
bipush 10:将整数 10 入栈 -
istore_1:将栈顶数据(int,10),保存到局部变量表下标为 1 的 slot
-
iload_1:将局部变量表中 slot 1 的数据(int,10),加载到操作数栈的栈顶 -
iinc 1, 1:将 slot 1 的整数自增 1(10 → 11)
-
iinc 1, 1:将 slot 1 的整数自增 1(11 → 12) -
iload_1:将局部变量表中 slot 1 的数据(int,12),加载到操作数栈的栈顶
-
iadd:将操作数栈的两个整数相加并出栈,求和结果(22)入栈 -
iload_1:将局部变量表中 slot 1 的数据(int,12),加载到操作数栈的栈顶 -
iinc 1, -1:将 slot 1 的整数自增 -1(12 → 11)
-
iadd:将操作数栈的两个整数相加并出栈,求和结果(34)入栈 -
istore_2:将栈顶数据(int,34),保存到局部变量表下标为 2 的 slot
到此,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; }
}
编译器按从上往下的顺序,收集所有静态结构(静态代码块,静态成员)的赋值语句,合并为 方法
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 块)
- 发生与 catch 块匹配的异常时,跳转到 target(第 8 行)
a_store2:将异常对象的引用放入 slot2。- 执行该 catch 块的内容,结束。
- 没有异常,执行完 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 块)
- 发生与 catch 块匹配的异常时,跳转到 target
- ArithmeticException:跳转到第 8 行
- NullPointerException:跳转到第 15 行
- Exception:跳转到第 22 行
astore_2:将异常对象的引用放入 slot2。- 由于只会匹配并进入一个异常分支,所以 slot2 公用。
- 执行该 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 块)
- 发生与 multi-catch 块匹配的异常时,跳转到 target(第 25 行)
astore_1:将异常对象的引用放入 slot1。- 执行该 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: ...
- finally 块的字节码指令,被复制并分别放入 try 块、catch 块。
- 增加 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: ...
- finally 块代码对应的字节码指令,被复制并分别放入 try 块、catch any 块。
- ireturn:将栈顶的整数作为返回值返回
- 与上一个案例中的 finally 相比,any 异常不会被 athrow 抛出。
- 结论:当 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: ...
- finally 块代码对应的字节码指令,被复制并分别放入 try 块、catch any 块。
- 注意
- slot0 和 slot1 都存储了变量 i,分别用于值的修改和返回值。
- slot0:当 finally 块中修改 i 时,修改的是 slot0 的 i;
- slot1:当方法返回时,返回的是 slot1 的i。
- 说明:i 值不会被修改,相当于锁定了返回值。
- 结论:当 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: ...
- 进入同步代码块后,会复制一份被加锁的对象并存储在局部变量表,用于解锁。
- monitorenter 加锁,monitorexit 解锁。加锁和解锁指令成对出现。
- 不论是否会出现异常,monitor 都会将对象引用解锁。
- 注:方法级别的 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。
- 获取字符串的 hashCode,比较 hashCode 和 equals()。
- 根据第一个 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 块
- 创建资源对象
- 在 try 内部再使用 try-catch 语句执行外部 try 块代码,若捕获异常则抛出。
- 若无异常,在 finally 中判断资源是否为空,非空则需要释放。
- 判断内部 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 的。
原因:局部变量作为构造器参数传入额外类,并且额外类生成后就不会再改变,因此局部变量也不允许改变。