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

问题描述 投票:11回答:1

考虑以下类。

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

java java-8 javac java-11 ecj
1个回答
38
投票

由于该字段不仅是 final但是,a 编译时常数当被读取时,它不会被访问,但读取会被常量值本身取代,即 iconst_5 在你的情况下,指令。

但在你的案例中,抛出一个 NullPointerException 当解除引用 null,这在使用 getfield 指令,必须保留¹。所以当你把方法改为

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

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

所以我们在这里看到的是 javac 没有认识到,在这一具体案例中,当提到的是 thisJVM保证了非空属性,因此,显式空检查是不必要的。

JLS §15.11.1. 使用主要的字段访问:

  • 如果该字段不在 static:

    • 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 小学 表达式被评估。如果评估了 小学 表达式突然完成,字段访问表达式也会因为同样的原因突然完成。
    • 如果字段访问表达式中的 小学null那么,a NullPointerException 被抛出。
    • 如果该字段为非空的 final的成员字段的值,那么结果就是类型为 T 的值所引用的对象中找到的。小学.

© www.soinside.com 2019 - 2024. All rights reserved.