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

2022-09-01 04:39:08

请考虑以下类:

class Temp {
    private final int field = 5;

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

然后我编译并反编译该类:

> 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)

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

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

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

int sum() {
    return 1 + 5;
}

所以这是javac特定的行为。


答案 1

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

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

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

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

因此,我们在这里看到的是,没有认识到在这种特定情况下,当引用是 时,非空属性由JVM保证,因此,显式空检查是不必要的。javacthis

¹ 参见 JLS §15.11.1。使用主数据库进行现场访问

  • 如果该字段不是 :static

    • 计算表达式。如果表达式的计算突然完成,则字段访问表达式会出于同样的原因突然完成。
    • 如果节点的值为 ,则抛出 a。nullNullPointerException
    • 如果该字段是非空白的,则结果是命名成员字段的值,该字段的类型位于由 Primary 的值所引用的对象中。finalT

    ...


答案 2

推荐