为什么非静态类变量在运行时初始化,而不是在Java编译时初始化

为什么非静态类变量在运行时初始化,而不是在Java编译时初始化,java,jvm,Java,Jvm,这是问题的延伸: 因此,在代码块中: public class Point { int y = getX(); int x = 42; int getX() { return x; } public static void main (String s[]) { Point p = new Point(); System.out.println(p.x + "," + p.y); } } 它输出42,0 虽然上面的问题是通过描述Java编译器和运行时的行为来回

这是问题的延伸:

因此,在代码块中:

public class Point {

int y = getX();
int x = 42;

int getX() { 
    return x; 
}

public static void main (String s[]) {
    Point p = new Point();
    System.out.println(p.x + "," + p.y);
}
}

它输出
42,0

虽然上面的问题是通过描述Java编译器和运行时的行为来回答的,但它仍然困扰着我为什么编译器不将x(42)的初始值烘焙到字节码中? 我知道静态变量的值会嵌入字节码,因为它们是类级变量,并且不会占用对象内存中的任何空间,但是也将类级非静态变量的任何初始值嵌入字节码难道没有意义吗?这样,上面的代码将更符合预期的行为,并且我猜对象的实例化将更快(因为分配给x的内存将立即包含42个,因此每次创建类Point的对象时,都可以节省解析类中初始化行的时间)

我怀疑这可能与类字节码大小、对象初始化效率和编译时效率之间的权衡有关

我希望对Java编译器/运行时有深入了解的人能对此有所启发。
了解框架的内部工作方式总是有助于我们编写更好的代码:-)

这不是效率的问题;这是一个具有合理语义的问题

Java设计者希望初始化的行为是相同的,无论
x
被定义为
42
还是
getFortyTwo()
,因为如果这种行为不同,那么就会导致各种各样的方式无意中射中自己的脚。因此,他们在JLS中指定了在哪个顺序字段中进行初始化,并且该顺序与
x
是常量还是方法调用或马铃薯无关。(作为参考,这是您在中声明字段的顺序——因此,如果您在类中颠倒了
x
y
的顺序,
y
将设置为42。)

坦率地说,我敢打赌编译器会将赋值
y=0
烘焙到构造函数中,因为JLS的语义要求基于此代码的
y=0

虽然上面的问题是通过描述Java编译器和运行时的行为来回答的,但它仍然困扰着我为什么编译器不将x(42)的初始值烘焙到字节码中

想象一下,如果你后来改变了这一点:

int x = 42;
为此:

int x = getInitialValueForX();
你真的想彻底改变这种行为吗


如果
x
实际上只是一个常量,那么将其设为
static final
并将其内联。事实上,如果一个变量不是最终变量,也不是静态变量,那么我不明白为什么它应该被视为常量,即使此时初始值恰好是常量。

我认为你误解了常量会发生什么,以及为什么

编译时常量可以是内联的,但通常静态变量不是。简单地说,编译时常量是编译时已知的基元类型的值。有许多静态变量是用非编译时常量的表达式初始化的


在这种情况下,
Point
getX()
不是最终结果,因此编译时不知道
getX()
的结果。当一个子类可以重写它并返回一个不同的值时,它怎么可能是内联的呢?

有趣的是,我们选择了相同的“如果是这种情况,那么奇怪的行为”示例:)我并不太惊讶。图书馆设计师(我认为包括我们两人在内的一个群体)倾向于本能地思考“如果这是不同的/你建议的方式,那怎么会回来困扰你/导致意外行为?”这里的答案是,任何不明显的常量都可能导致语义混乱/意外——而非常量的最佳来源是方法调用。如果变量是对象,它不会产生问题吗?当值为42时一切都很好,但是如果值是ComplexNumber类的对象呢?我们是否希望所有的点都指向相同的内存地址,本质上是指相同的复数?现在我不是一个对Java编译器/运行时有深入了解的人,所以我可能在这里完全错了,但这是我能想到的最好的理由。它确实“将x(42)的初始值烘焙到字节码中”。你认为x怎么会变成42岁?在创建实例之前,它不会将其烘焙到实例内存中,因为没有实例内存。在运行时。你的问题没有道理。