Java:对象初始化序列

5

有一段代码是给初级Java开发人员的任务。我已经使用Java五年了,但这段代码完全让我困惑:

public class Main {

    String variable;

    public static void main(String[] args) {
        System.out.println("Hello World!");
        B b = new B();
    }

    public Main(){
        printVariable();
    }

    protected void printVariable(){
        variable = "variable is initialized in Main Class";
    }
}

public class B extends Main {

    String variable = null;

    public B(){
        System.out.println("variable value = " + variable);
    }

    protected void printVariable(){
        variable = "variable is initialized in B Class";
    }
}

输出结果将为:
Hello World!
variable value = null

但是如果我们将String variable = null;更改为String variable;,我们会得到:

Hello World!
variable value = variable is initialized in B Class

第二个输出对我来说更清晰。就我所知,Java的初始化顺序如下:
  • 我们进入类层次结构的根(对于Java,它始终是Object类),当我们到达此根父类时:
    • 所有静态数据字段都被初始化;
    • 执行所有静态字段初始值设定项和静态初始化块;
    • 初始化所有非静态数据字段;
    • 执行所有非静态字段初始值设定项和非静态初始化块;
    • 执行默认构造函数;
  • 然后我们为底层子类重复该过程。

此外,还有一篇文章描述了this关键字在超类上下文中的行为 - 从基类方法调用基类覆盖函数

根据上述规则,我假设序列如下:

  1. 我们将创建类B的一个新实例;
  2. 我们转到类Main部分;
  3. 用null初始化main.variable
  4. 然后我们移动到类Main的默认构造函数;
  5. 构造函数在类Main中调用方法b.printVariable();(为什么不调用main.printvariable?这里没有使用this关键字。)
  6. 字段b.variable"变量在B类中初始化"
  7. 现在我们回到类B;
  8. 我们应该使用null值初始化字段b.variable,是吗?;
  9. 执行类B的默认构造函数

请问有人可以完整详细地解释一下这个继承初始化顺序是如何工作的吗?以及为什么将String variable = null;更改为String variable;会导致另一种输出。


1
printVariable() 是一个相当误导性的方法名 - 应该更像是 setVariable()。 - Jimmt
1
你已经写了5年的Java,却不知道如何使用调试器吗?(逐步执行代码将会准确展示发生了什么以及顺序) - Brian Roach
@BrianRoach 当然可以使用调试器,我也尝试过 javap -v -c B.class。但每当我看到这样的 Java 开发人员任务或面试问题时,我都会尝试预测输出并理解它,为什么它会像这样工作以及如果我稍微修改代码会发生什么。逐步执行无法解释执行过程的规则和原因。 - INlHELL
3个回答

9
序列如下:
  1. Main -> "Hello"
  2. Main -> new B()
  3. B() -> Main() -> b.printVariable() -> 设置变量
  4. 回到初始化B,因此variable=null出现。
所以基本上,在类B的任何初始化事件之前,都会构造超级对象Main()。这意味着variable=null发生在稍后。这是有道理的,否则B可能会破坏Main的初始化。
乔舒亚·布洛赫在他的effective java书中涵盖了很多关于正确使用继承的重要内容,我强烈推荐一读。

非常感谢您的解释,还要非常感谢您提到的书籍,我会认真再读一遍。 - INlHELL

2

首先,你需要了解当你写下variable = null;时会发生什么。这段代码何时执行将决定输出结果。

在我开始之前,我应该提到,当你创建一个B类的对象时,主类的printVariable()函数不会被调用。相反,总是会调用B的printVariable()

记住,在你有variable = null时,执行B的构造函数将开始。首先会调用Main(),它将调用printVariable()方法。最后,variable=null将被调用,覆盖variable变量。

在另一种情况下,如果你没有初始化variable=null,则由printVariable()函数设置的variable不会被覆盖,因此你将得到你期望的结果。

总之,当你执行new B()时,这是语句的执行顺序:

Main()     //super constructor
  B#printVariable()
  initializtion of variables in B's constructor (if any) [i.e. variable=null, if present]

非常感谢,您的解释完全澄清了这段代码。我之前没有理解到,原来可以这样初始化字段:super_class->method_of_child_class->field_of_child_class并且在子类初始化过程中,如果该字段不为 null,则可能被覆盖。 - INlHELL

1
这是一个不错的练习!但对于初级开发人员来说,这并不是一个公平的问题。这个问题适合高级开发人员。但为了使这段文字在技术面试中有用,我会通过向Main构造函数添加一个参数来进行修改:
public Main(String something){
 printVariable();
}

如果这个人回答了问题,那么就移除参数并继续问原来的问题。如果这个人不回答 - 就没有必要继续了 - 他/她是初学者。
您也可以移除类B中的protected限定符,并询问如果您有一个不雇用这个人的目标会发生什么 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接