什么是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() { // ..... } }