【问题标题】:Why does javac insert Objects.requireNonNull(this) for final fields?为什么 javac 为最终字段插入 Objects.requireNonNull(this)?
【发布时间】:2020-10-02 01:34:02
【问题描述】:

考虑以下类:

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

【问题讨论】:

  • 似乎,它没有意识到与其他表达式相比,this 没有必要这样做。
  • 好收获(+1)。除此之外,javac 15-ea 也可以观察到相同的行为。进一步观察,删除final 工作正常。
  • 试图放入评论中,int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object; 5: pop 6: iconst_5 7: iadd 8: ireturn 与删除解析为 int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: getfield #7 // Field field:I 5: iadd 6: ireturn 的最终关键字时相比

标签: java java-8 javac java-11 ecj


【解决方案1】:

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

但必须保留在取消引用 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:

  • 如果字段不是static:

    • Primary 表达式被求值。如果 Primary 表达式的求值突然完成,则字段访问表达式出于同样的原因突然完成。
    • 如果Primary 的值为null,则抛出NullPointerException
    • 如果该字段是非空的final,则结果是类型为T 的命名成员字段的值,该字段在Primary 的值所引用的对象中找到。

【讨论】:

  • 好点。像这样演示更容易:Temp t = null; return 1 + t.fieldObjects.requireNonNull(t),否则返回 6 而没有 NPE。
  • @MTilsted:不,因为在这种情况下,字段本身是一个原始int,不能为空。
  • @MTilsted 使用反射更改最终字段可能会导致未定义的行为。可能不会影响内联值就是其中之一。
  • @MTilsted 检查与字段值无关。它与字段所有者参考有关,即this。引用不能通过Reflection改变,也不可能是null,因为不可能进入带有nullreceiver引用的实例方法;调用指令已经失败了。
  • @TomHawtin-tackline 从使用getClassrequireNonNull 的转换已在this Q&A 中得到解决。
猜你喜欢
  • 2018-01-19
  • 2016-10-05
  • 1970-01-01
  • 2023-02-11
  • 2020-04-09
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
相关资源
最近更新 更多