38

考虑以下类:

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

简而言之,javac编译sum()为:

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 特有的行为。

4

1 回答 1

41

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

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

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

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

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

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

  • 如果该字段不是static

    • 评估主表达式。如果Primary表达式的求值突然完成,则字段访问表达式出于相同的原因突然完成。
    • 如果Primary的值为null,则NullPointerException抛出 a。
    • 如果该字段是非空的final,则结果是在PrimaryT的值所引用的对象中找到的指定成员字段的值。

    …</p>

于 2020-06-12T09:10:04.557 回答