Java 中的断言是如何实现的

69 阅读2分钟

Java 中的断言是如何实现的

结论

  1. 在编译生成的 class 文件中,会有对应的静态合成字段表示在这个类中断言是否关闭。
  2. 在运行时,静态初始化语句块会对这个合成字段赋值。

代码

我们用以下的代码来进行探索 (请将代码保存为 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();
  }
}

可见,编译器会

  1. 合成一个静态字段用于表示断言是否关闭 (在这个例子里是 $assertionsDisabled 字段)
  2. 在用到断言的地方添加对应的 if 判断 (在这个例子里 add(int, int) 方法中多了一些 if 判断)

我们用以下命令分别来运行 SimpleAdder 类中的 main(...) 方法。

java -enableassertions SimpleAdder
java -disableassertions SimpleAdder

对应的结果如下

image.png