细数Java的语法糖(二): 条件编译、断言语句

本文介绍的两个语法糖均与条件相关,由于它们并不太常见,所以只会用很短的篇幅介绍。

条件编译

一般情况下,每一行代码都要参与编译。但有时,出于某种原因,可能只希望对代码的一部分进行编译。这时,需要在程序中加上条件,编译器可以只对满足条件的代码进行编译,并丢弃不满足条件的部分,这就是条件编译。

诸如 C、C++ 等许多语言提供了预处理的功能,并通过预处理来实现条件编译。例如下面这段 C 代码在 DEBUG 模式编译代码块1,其他情况编译代码块2:

    #ifdef DEBUG
        code block 1
    #else
        code block 2
    #endif
复制代码

Java 并没有提供类似的预处理功能,但是 Java 也可以实现条件编译。考虑如下代码:

    private static final boolean DEBUG = true;

    public static void main(String[] args) {
        if (DEBUG) {
            System.out.println("block 1");
        } else {
            System.out.println("block 2");
        }
    }
复制代码

反编译字节码文件得到 main 方法的实际内容:

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String block 1
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
复制代码

换句话说,编译后的 main 方法与下面的代码等价:

    public static void main(String[] args) {
        System.out.println("block 1");
    }
复制代码

可以看到,编译器会做优化,对于条件必然为 false 的语句块,编译器不会为其生成字节码。

由以上可见,Java 的条件编译是借助条件为常量的条件语句来实现的,因此,Java 的条件编译只能局限在语句块级别。相比之下,C、C++ 等语言由于有预处理,因此可以实现文件级别的条件编译。

断言语句

Java 1.4 引入了断言,用以指明程序到达某个执行点时所必然满足的条件。

考虑如下求倒数的代码,我们使用断言来指明所指定的数字不能为 0:

public class Test {
    public static void main(String[] args) {
        System.out.println(inverse(2.0));
    }

    private static double inverse(double d) {
        assert d != 0;
        return 1.0 / d;
    }
}
复制代码

反编译,得到(此处省去了构造方法和main方法):

public class com.aliyun.yqtest.Test {
  static final boolean $assertionsDisabled;

  private static double inverse(double);
    Code:
       0: getstatic     #7                  // Field $assertionsDisabled:Z
       3: ifne          20
       6: dload_0
       7: dconst_0
       8: dcmpl
       9: ifne          20
      12: new           #8                  // class java/lang/AssertionError
      15: dup
      16: invokespecial #9                  // Method java/lang/AssertionError."<init>":()V
      19: athrow
      20: dconst_1
      21: dload_0
      22: ddiv
      23: dreturn

  static {};
    Code:
       0: ldc           #10                 // class com/aliyun/yqtest/Test
       2: invokevirtual #11                 // 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
}
复制代码

可以看到,编译过后,方法所处的类中多了一个 static final boolean 字段 $assertionsDisabled ,并且还被添加了一段静态代码块用于初始化它。事实上,这个字段就是断言是否开启的标识。在 inverse 方法内部,先根据 $assertionsDisabled 字段检查断言是否开启。如果未开启,则直接跳转到指令 20,继续执行求倒数的逻辑;如果开启,则检查条件是否满足,如果条件满足则跳至指令 20,否则创建一个 AssertionError 并抛出。简单地讲,编译后的 inverse 方法可以看做下面这样:

    private static double inverse(double d) {
        if (!$assertionsDisabled && d == 0) {
            throw new AssertionError();
        }
        return 1.0 / d;
    }
复制代码

总结一下,断言在编译过后被转化成了语句所处类中的一个 static final boolean 字段,并在类初始化阶段借助静态语句块完成其初始化。在程序执行过程中,直接根据该字段判断断言是否开启,来决定是否执行断言检查。

References

  1. baike.baidu.com/item/%E6%9D…
  2. baike.baidu.com/item/%E6%96…



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值