为什么 javac 为最终字段插入 Objects.requireNonNull(this) ?

Zhe*_*lov 38 java javac java-8 ecj java-11

考虑以下类:

class Temp {
    private final int field = 5;

    int sum() {
        return 1 + this.field;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我编译和反编译这个类:

> javac --version
javac 11.0.5

> javac Temp.java

> javap -v Temp.class
  ...
  int sum();
    descriptor: ()I
    flags: (0x0000)
    Code:
      stack=2, locals=1, args_size=1
         0: iconst_1
         1: aload_0
         2: invokestatic  #3   // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
         5: pop
         6: iconst_5
         7: iadd
         8: ireturn
Run Code Online (Sandbox Code Playgroud)

简单来说,javac编译sum()成这样:

int sum() {
    final int n = 1;
    Objects.requireNonNull(this); // <---
    return n + 5;
}
Run Code Online (Sandbox Code Playgroud)

什么是Objects.requireNonNull(this)在这里做什么?重点是什么?这是否与可达性有某种联系?

Java 8 编译器与此类似。它插入this.getClass()而不是Objects.requireNonNull(this)

int sum() {
    final int n = 1;
    this.getClass(); // <---
    return n + 5;
}
Run Code Online (Sandbox Code Playgroud)

我也尝试用 Eclipse 编译它。它不插入requireNonNull

int sum() {
    return 1 + 5;
}
Run Code Online (Sandbox Code Playgroud)

所以这是 javac 特定的行为。

Hol*_*ger 41

由于该字段不仅是final,而且是编译时常量,因此在读取时不会访问它,但读取会被常量值本身替换,即iconst_5您的情况下的指令。

但是,NullPointerException在取消引用时抛出 a 的行为(null在使用getfield指令时隐含)必须保留¹。因此,当您将方法更改为

int sumA() {
  Temp t = this;
  return 1 + t.field;
}
Run Code Online (Sandbox Code Playgroud)

Eclipse 也会插入一个显式的空检查。

因此,我们在这里看到的是javac未能认识到在这种特定情况下,当引用为 时this,非空属性由 JVM 保证,因此,不需要显式空检查。

¹ 参见JLS §15.11.1。使用主字段访问

  • 如果该字段不是static

    • 所述表达式。如果Primary表达式的计算突然完成,则字段访问表达式也会出于同样的原因突然完成。
    • 如果Primary 的值为null,则NullPointerException抛出 a。
    • 如果该字段是非空的final,则结果是在TPrimary的值引用的对象中找到的type 中的命名成员字段的值。

  • 好点子。像这样演示更容易:`Temp t = null; 返回 1 + t.field`。`Objects.requireNonNull(t)`,否则会返回 6,没有 NPE。 (2认同)