为什么这些字段在可能首次使用之前没有初始化?
Why are not the fields initialized before their possible first use?
当我发现 Java 中的字段初始化有一些奇怪的顺序时,我感到非常困惑。
init() 结果被字段初始化覆盖时的示例代码:
public abstract class Parent {
public String parentField = "dupa";
public Parent(){
init(); // uhh, bad practice to call abstract method in a super constructor
}
protected abstract void init();
}
public class Child extends Parent {
public String childField = null; // assigning null is unnecessary, another bad practice
@Override
protected void init(){
childField = "initialized";
System.out.println("After init(): " + childField);
}
}
...
Child child = new Child(); // OUTPUT: After init(): initialized
System.out.println("After all: " + child.childField); // OUTPUT: After all: null
我发现调用时的执行顺序是什么 new Child();
:
- Parent 字段初始化,但 childField 已经存在并具有默认值 (childField = null)
- Parent构造函数
- 被 parent 构造函数调用的重写 init() (childField = "initialized")
- Child 字段初始化:childField = null(再次)
- Child构造函数
我知道这个例子充满了不好的做法。然而,对我来说直观的顺序是:字段初始化(从 parents 到 childs),然后是构造函数(从 parents 到 childs)。
这样的初始化顺序有什么用?
为什么字段初始值设定项在首次使用之前不执行?
如果该字段还没有初始化,为什么允许使用它?
让我解释一下你的 "intuitive" 构建新 object 的顺序:
- Parent 的字段已初始化
- Child 的字段已初始化
- Parent的构造函数被调用
- Child的构造函数被调用
嗯,这不太合理,因为 child 字段的初始化可能依赖于 parent。
一个object的构造函数returns可以认为是"properly initialized"。同意吗?
我们不使用 Parent
和 Child
,而是使用 Box
和 TreasureBox
。要构建 TreasureBox
,您首先要构建 Box
。盒子做好后,你可以给它加上不同的装饰,让它看起来很酷很酷,你也可以加一把锁什么的。
看到了吗?这里的顺序?在初始化 child 之前首先正确初始化 parent 是最有意义的,这意味着 child class 的任何初始化必须在 parent 之后进行的构造函数 returns。这正是 Java 正在做的事情。
child 的字段可以依赖于 parent 的字段。要给 TreasureBox
上锁,你需要找到盒子的正面,然后把它放在那里。如果盒子的正面还没有创建,你怎么给它加锁?
这里有一些代码可以阐明我的意思:
class Parent {
public String parentField;
public Parent(){
parentField = "Hello";
}
}
class Child extends Parent {
public int childField = parentField.length();
}
如果Java使用您的"intuitive"订单,将抛出NPE。
当我发现 Java 中的字段初始化有一些奇怪的顺序时,我感到非常困惑。 init() 结果被字段初始化覆盖时的示例代码:
public abstract class Parent {
public String parentField = "dupa";
public Parent(){
init(); // uhh, bad practice to call abstract method in a super constructor
}
protected abstract void init();
}
public class Child extends Parent {
public String childField = null; // assigning null is unnecessary, another bad practice
@Override
protected void init(){
childField = "initialized";
System.out.println("After init(): " + childField);
}
}
...
Child child = new Child(); // OUTPUT: After init(): initialized
System.out.println("After all: " + child.childField); // OUTPUT: After all: null
我发现调用时的执行顺序是什么 new Child();
:
- Parent 字段初始化,但 childField 已经存在并具有默认值 (childField = null)
- Parent构造函数
- 被 parent 构造函数调用的重写 init() (childField = "initialized")
- Child 字段初始化:childField = null(再次)
- Child构造函数
我知道这个例子充满了不好的做法。然而,对我来说直观的顺序是:字段初始化(从 parents 到 childs),然后是构造函数(从 parents 到 childs)。
这样的初始化顺序有什么用? 为什么字段初始值设定项在首次使用之前不执行? 如果该字段还没有初始化,为什么允许使用它?
让我解释一下你的 "intuitive" 构建新 object 的顺序:
- Parent 的字段已初始化
- Child 的字段已初始化
- Parent的构造函数被调用
- Child的构造函数被调用
嗯,这不太合理,因为 child 字段的初始化可能依赖于 parent。
一个object的构造函数returns可以认为是"properly initialized"。同意吗?
我们不使用 Parent
和 Child
,而是使用 Box
和 TreasureBox
。要构建 TreasureBox
,您首先要构建 Box
。盒子做好后,你可以给它加上不同的装饰,让它看起来很酷很酷,你也可以加一把锁什么的。
看到了吗?这里的顺序?在初始化 child 之前首先正确初始化 parent 是最有意义的,这意味着 child class 的任何初始化必须在 parent 之后进行的构造函数 returns。这正是 Java 正在做的事情。
child 的字段可以依赖于 parent 的字段。要给 TreasureBox
上锁,你需要找到盒子的正面,然后把它放在那里。如果盒子的正面还没有创建,你怎么给它加锁?
这里有一些代码可以阐明我的意思:
class Parent {
public String parentField;
public Parent(){
parentField = "Hello";
}
}
class Child extends Parent {
public int childField = parentField.length();
}
如果Java使用您的"intuitive"订单,将抛出NPE。