具有继承调用的构造函数中的最终变量

时间:2015-03-30 13:28:11

标签: java

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

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
}

最后,对我来说更奇怪(我认为这与String #intern机制相关)

public static abstract class A {

    public A() 
    {
        doSomething();
    }

    public abstract void doSomething();

}

public static class B extends A {

    private final String s = "Hello"; 

    public B()
    {
    }

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

public static void main( String[] args )
{
    new B(); // will print "Hello"
}

我的问题是,如果我使用确保非空值的getter,我可以在第一种情况下做些什么来解决这个问题?

我理解为什么第一种情况发生(构造函数在初始化任何实例变量之前隐含地调用'super'构造函数),但是,如果我是正确的,在这种情况下为什么第3种情况正确打印'Hello' ?

1 个答案:

答案 0 :(得分:2)

理解基类的构造函数在子类的构造函数之前执行是很重要的。这意味着在构造基类期间可能尚未初始化子类的字段。 (然而, 在构建子类时初始化。)

  

我的问题是,如果我使用确保非空值的getter,我可以在第一种情况下做些什么来解决这个问题?

您发现的问题是从未在构造函数中调用可覆盖方法的原因之一。

吸气剂可能同样糟糕,因为吸气剂也会被覆盖。

而不是

Object s = new Object();

...

public void doSomething() {
    System.out.println(s);
}
<{1>}中的

,您可以将B构造中使用的变量作为A构造函数的参数传递:

A

这传递了与构造public B() { super(new Object()); } 对象相关的数据,因此B的构造函数是“自包含的”。这很麻烦,我建议你重新考虑你的课程结构。


关于第三种情况:

B

由于private final String s = "Hello"; 是编译时常量表达式,并且"Hello"是最终的,因此Java编译器可以自由地使用s,即将s替换为s可自行决定。