什么是Java中的对象字段初始化和构造函数顺序

我今天早些时候在代码中结束了以下场景(我承认有点奇怪,我已经重构了)。 当我运行unit testing时,我发现在超类构造函数运行时没有设置字段初始化。 我意识到我并不完全理解构造函数/字段初始化的顺序,所以我希望有人向我解释这些发生的顺序。

class Foo extends FooBase { String foo = "foobar"; @Override public void setup() { if (foo == null) { throw new RuntimeException("foo is null"); } super.setup(); } } class FooBase { public FooBase() { setup(); } public void setup() { } } @Test public void testFoo() { new Foo(); } 

来自JUnit的缩写回溯如下,我想我期望$ Foo。来设置foo。

 $Foo.setup $FooBase. $Foo. .testFoo 

是的,在Java中(例如,与C#不同)字段初始值设定项在超类构造函数之后调用。 这意味着在执行字段初始值设定项之前 ,将调用构造函数的任何重写方法调用。

订购是:

  • 初始化超类(递归调用这些步骤)
  • 执行字段初始值设定项
  • 执行构造函数体(在任何构造函数链接之后,已在步骤1中执行)

基本上,在构造函数中调用非final方法是个坏主意。 如果您打算这样做,请将其清楚地记录下来,以便覆盖该方法的任何人都知道在执行字段初始化程序(或构造函数体)之前将调用该方法。

有关更多详细信息,请参阅JLS第12.5节 。

构造函数的第一个操作始终是超类构造函数的调用。 没有在类中明确定义的构造函数等同于具有

 public Foo() { super(); } 

因此,在初始化子类的任何字段之前调用基类的构造函数。 你的基类做了一些应该避免的事情:调用一个可覆盖的方法。

由于此方法在子类中被重写,因此它在尚未完全构造的对象上调用,因此将子类字段视为null。

这是伪C#/ Java中的多态性示例:

 class Animal { abstract string MakeNoise (); } class Cat : Animal { string MakeNoise () { return "Meow"; } } class Dog : Animal { string MakeNoise () { return "Bark"; } } Main () { Animal animal = Zoo.GetAnimal (); Console.WriteLine (animal.MakeNoise ()); } 

Main函数不知道动物的类型,并且取决于MakeNoise()方法的特定实现的行为。

 class A { A(int number) { System.out.println("A's" + " "+ number); } } class B { A aObject = new A(1); B(int number) { System.out.println("B's" + " "+ number); } A aObject2 = new A(2); } public class myFirstProject { public static void main(String[] args) { B bObj = new B(5); } } 

out:A的1 A是2 B的5

我的规则:1。不要使用声明中的默认值进行初始化(null,false,0,0.0 …)。 2.如果没有更改字段值的构造函数参数,则首选声明中的初始化。 3.如果由于构造函数参数而改变字段的值,则将初始化放在构造函数中。 4.在练习中保持一致。 (最重要的规则)

 public class Dice { private int topFace = 1; private Random myRand = new Random(); public void Roll() { // ...... } } 

要么

 public class Dice { private int topFace; private Random myRand; public Dice() { topFace = 1; myRand = new Random(); } public void Roll() { // ..... } }