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

Why does javac insert Objects.requireNonNull(this) for final fields?

考虑以下 class:

class Temp {
    private final int field = 5;

    int sum() {
        return 1 + this.field;
    }
}

然后我编译和反编译 class:

> 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

简单来说,javacsum() 编译为:

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

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

Java8编译器类似。它插入 this.getClass() 而不是 Objects.requireNonNull(this):

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

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

int sum() {
    return 1 + 5;
}

所以这是特定于 javac 的行为。

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

但是必须保留在取消引用 null 时抛出 NullPointerException 的行为,这在使用 getfield 指令时会被暗示。因此,当您将方法更改为

int sumA() {
  Temp t = this;
  return 1 + t.field;
}

Eclipse 也会插入显式空检查。

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

¹ 见 JLS §15.11.1. Field Access Using a Primary:

  • If the field is not static:

    • The Primary expression is evaluated. If evaluation of the Primary expression completes abruptly, the field access expression completes abruptly for the same reason.
    • If the value of the Primary is null, then a NullPointerException is thrown.
    • If the field is a non-blank final, then the result is the value of the named member field in type T found in the object referenced by the value of the Primary.