Java 构造函数中具有继承调用的最后一个变量

Java 构造函数中具有继承调用的最后一个变量,java,Java,我刚刚发现了一些很奇怪的事情。如果使用重写方法从隐式超级构造函数调用最终变量,则在调用时永远不会初始化元素: public static abstract class A { public A() { doSomething(); } public abstract void doSomething(); } public static class B extends A { private final Object s = n

我刚刚发现了一些很奇怪的事情。如果使用重写方法从隐式超级构造函数调用最终变量,则在调用时永远不会初始化元素:

public static abstract class A {

    public A() 
    {
        doSomething();
    }

    public abstract void doSomething();

}

public static class B extends A {

    private final Object s = new Object(); 

    public B()
    {
    }

    public void doSomething() {
        System.out.println(s);
    }
}

public static void main( String[] args )
{
    new B();// prints 'null'
}
如果未重写该方法,则将正确实例化最终变量:

public static class B  {

    private final Object s = new Object(); 

    public B()
    {
        doSomething();
    }

    public void doSomething() {
        System.out.println(s);
    }
}

public static void main( String[] args )
{
    new B(); // prints the object correctly
}
最后,对我来说更奇怪的是(我认为这与字符串#intern机制有关)

我的问题是,在第一种情况下,我能做些什么来解决这个问题,我应该使用确保非空值的getter吗


我有点理解为什么会出现第一种情况(构造函数在初始化任何实例变量之前隐式调用“super”构造函数),但是,如果我是正确的,在这种情况下,为什么第3个案例正确地打印“Hello”?

重要的是要理解基类的构造函数是在子类的构造函数之前执行的。这意味着,在基类的构造过程中,子类的字段可能尚未初始化。(但是,它们将在子类的构造过程中初始化。)

我的问题是,在第一种情况下,我能做些什么来解决这个问题,我应该使用确保非空值的getter吗

您发现的问题是永远不要从构造函数中调用可重写方法的原因之一

getter可能同样糟糕,因为getter也是可重写的

而不是

Object s = new Object();

...

public void doSomething() {
    System.out.println(s);
}
B
中,您可以将用于
A
构造的变量作为参数传递给
A
s构造函数:

public B() {
    super(new Object());
}
这将传递与构造
B
对象相关的数据,以便
B
的构造函数是“自包含的”。这是相当混乱的,我建议你重新考虑你的课程结构


关于第三个案件:

private final String s = "Hello";

由于
“Hello”
是一个编译时常量表达式,而且
s
是最终表达式,因此Java编译器可以自由地内联使用
s
,也就是说,可以随意将
s
替换为
“Hello”

我认为第三种情况基本上是一种优化,因为它是用常量值初始化的。尝试使用
“Hello”.toString()
,我想您会看到不同的结果。“修复”不是从构造函数中调用非final方法……我希望看到一个具体的例子(真实的类-不是
a
B
),其中这是一个实际问题。我是在一个真实的案例之后创建这些类的application@JonSkeet-什么时候有人会使用“你好”.toString()?@TheLostMind:当他们试图演示常量值和非常量值之间的差异时:),但这太混乱了。也许值得在最后一行详细说明。我刚刚检查过,看起来这个字段在调用时并没有初始化——但是Java编译器正在方法调用中内联
s
的值。我想这就是你的意思,但更清楚一点是好的:)@Qix,我同意。但我想说,这是让子类影响基类构造的想法所固有的。@JonSkeet出于好奇,C#也可以“内联”使用编译时常量表达式。(请注意,我从未写过一行C#)如果您对您可以参考的建议解决方案不满意。如果这些答案也不令人满意(我认为这些答案中没有一个是正确的),你可以悬赏。我相信这会产生一些好的答案。
private final String s = "Hello";