Java 中的断言是如何实现的
结论
- 在编译生成的
class文件中,会有对应的静态合成字段表示在这个类中断言是否关闭。 - 在运行时,静态初始化语句块会对这个合成字段赋值。
代码
我们用以下的代码来进行探索 (请将代码保存为 SimpleAdder.java)
public class SimpleAdder {
public int add(int a, int b) {
assert a >= 0 : "参数 a 应当大于或等于 0";
assert b >= 0 : "参数 b 应当大于或等于 0";
return a + b;
}
public static void main(String[] args) {
System.out.println(SimpleAdder.class.desiredAssertionStatus());
SimpleAdder adder = new SimpleAdder();
int ans = adder.add(1, -1);
System.out.println(ans);
}
}
SimpleAdder 类中定义了 add(int, int) 方法,用于将两个非负整数相加。
我们用如下的命令来编译 SimpleAdder.java。
javac -g -parameters SimpleAdder.java
编译后,会得到 SimpleAdder.class 文件。
我们用如下命令来查看 SimpleAdder.class 里的内容。
javap -v -p SimpleAdder
部分结果展示如下 (常量池等内容略去)
static final boolean $assertionsDisabled;
descriptor: Z
flags: (0x1018) ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
public SimpleAdder();
descriptor: ()V
flags: (0x0001) 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 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LSimpleAdder;
public int add(int, int);
descriptor: (II)I
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=3, args_size=3
0: getstatic #7 // Field $assertionsDisabled:Z
3: ifne 20
6: iload_1
7: ifge 20
10: new #13 // class java/lang/AssertionError
13: dup
14: ldc #15 // String 参数 a 应当大于或等于 0
16: invokespecial #17 // Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
19: athrow
20: getstatic #7 // Field $assertionsDisabled:Z
23: ifne 40
26: iload_2
27: ifge 40
30: new #13 // class java/lang/AssertionError
33: dup
34: ldc #20 // String 参数 b 应当大于或等于 0
36: invokespecial #17 // Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
39: athrow
40: iload_1
41: iload_2
42: iadd
43: ireturn
LineNumberTable:
line 3: 0
line 4: 20
line 5: 40
LocalVariableTable:
Start Length Slot Name Signature
0 44 0 this LSimpleAdder;
0 44 1 a I
0 44 2 b I
StackMapTable: number_of_entries = 2
frame_type = 20 /* same */
frame_type = 19 /* same */
MethodParameters:
Name Flags
a
b
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // class SimpleAdder
5: invokevirtual #28 // Method java/lang/Class.desiredAssertionStatus:()Z
8: invokevirtual #34 // Method java/io/PrintStream.println:(Z)V
11: new #8 // class SimpleAdder
14: dup
15: invokespecial #40 // Method "<init>":()V
18: astore_1
19: aload_1
20: iconst_1
21: iconst_m1
22: invokevirtual #41 // Method add:(II)I
25: istore_2
26: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
29: iload_2
30: invokevirtual #45 // Method java/io/PrintStream.println:(I)V
33: return
LineNumberTable:
line 9: 0
line 10: 11
line 11: 19
line 12: 26
line 13: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
19 15 1 adder LSimpleAdder;
26 8 2 ans I
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #8 // class SimpleAdder
2: invokevirtual #28 // Method java/lang/Class.desiredAssertionStatus:()Z
5: ifne 12
8: iconst_1
9: goto 13
12: iconst_0
13: putstatic #7 // Field $assertionsDisabled:Z
16: return
LineNumberTable:
line 1: 0
StackMapTable: number_of_entries = 2
frame_type = 12 /* same */
frame_type = 64 /* same_locals_1_stack_item */
stack = [ int ]
利用以上结果,我们可以手动写出对应的 java 代码。
// 以下内容是我手工生成的,不保证绝对准确,仅供参考
public class SimpleAdder {
// $assertionsDisabled 是一个合成字段
static final boolean $assertionsDisabled;
public SimpleAdder() {
super();
}
public int add(int a, int b) {
if (!$assertionsDisabled) {
if (!(a >= 0)) {
throw new AssertionError("参数 a 应当大于或等于 0");
}
}
if (!$assertionsDisabled) {
if (!(b >= 0)) {
throw new AssertionError("参数 b 应当大于或等于 0");
}
}
return a + b;
}
public static void main(String[] args) {
System.out.println(SimpleAdder.class.desiredAssertionStatus());
SimpleAdder adder = new SimpleAdder();
int ans = adder.add(1, -1);
System.out.println(ans);
}
static {
$assertionsDisabled = !SimpleAdder.class.desiredAssertionStatus();
}
}
可见,编译器会
- 合成一个静态字段用于表示断言是否关闭 (在这个例子里是
$assertionsDisabled字段) - 在用到断言的地方添加对应的
if判断 (在这个例子里add(int, int)方法中多了一些if判断)
我们用以下命令分别来运行 SimpleAdder 类中的 main(...) 方法。
java -enableassertions SimpleAdder
java -disableassertions SimpleAdder
对应的结果如下