通过对象访问时静态最终字段和在声明时初始化的实例最终字段之间的差异

DIfference between a static final field and an instance final field initialized at declaration when accessed through an object

我的问题是

两者有什么区别? (不含创建时间)

我是不是漏掉了什么?

虽然final,但是JVM没有做任何优化和假设,所以class的每个实例都会有一个x的实例。如果声明成员 static,class 的每个实例将共享 x 的相同实例,因为它是静态分配的。此外,如所写(x 具有包可见性),其他 classes 可能会静态访问 x,即没有可用的 class 实例。

如果您为这两种类型的 classes 创建了多个实例,那么,

在第一种情况下,MyClass 的所有对象都将拥有自己的 final x 字段。

在第二种情况下,MyOtherClass 的所有对象都指向一个 final x class 字段,因为它本质上是静态的。

xMyClassMyOtherClass的区别是:

  • 第一个只能通过 MyClass 实例访问,并且常量可以有多个副本。

  • 第二个可以不带MyOtherClass实例访问,只能存在一个副本

在您的示例中,具有一个或多个常量实例之间没有实际区别1。但是考虑一下:

public class YetAnotherClass {
    final int x;

    public YetAnotherClass(int x) {
        this.x = x;
    }
}

... 这显示了实例常量如何在不同的实例中具有不同的值。


1 - 这是夸大其词。首先,static final int x = 3; 声明了一个编译时常量,编译时常量可以用在 switch case 表达式中,而非编译时常量不能。其次,常量的非静态版本将在 MyClass 的每个实例中占据 space。最后,如果您足够愚蠢地尝试使用反射来更改常量,那么行为将会有所不同。 (千万别做...)

区别: 静态属于 class 因此您可以在没有 class 的任何实例的情况下访问它,因此只有它的一个副本。

虽然要访问第二个,您需要 class 的实例来访问它,这样您就可以拥有与属于 Object 的非静态最终对象一样多的副本。

可以用字节码验证: 对于静态

Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

对于非静态决赛

Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_2
         6: putfield      #2                  // Field x:I
         9: return
      LineNumberTable:

如您所见,它有 putfield,它设置了 objectref(对对象的引用)中标识的字段的值