我今天早些时候在代码中结束了以下场景(我承认有点奇怪,我已经重构了)。当我运行单元测试时,我发现在超类构造函数运行的时候没有设置字段初始化。我意识到我并不完全理解构造函数/字段初始化的顺序,所以我希望有人向我解释这些发生的顺序。
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。< init>设置foo。
$Foo.setup
$FooBase.<init>
$Foo.<init>
.testFoo
答案 0 :(得分:7)
是的,在Java中(例如,与C#不同)字段初始值设定项在超类构造函数之后被称为。这意味着在执行字段初始值设定项之前,来自构造函数的任何重写方法调用都将被称为。
排序是:
基本上,在构造函数中调用非final方法是个坏主意。如果您打算这样做,请清楚地记录 very ,以便任何覆盖该方法的人都知道在执行字段初始值设定项(或构造函数体)之前将调用该方法。
有关详细信息,请参阅JLS section 12.5。
答案 1 :(得分:3)
构造函数的第一个操作始终是超类构造函数的调用。没有在类中明确定义的构造函数等同于
public Foo() {
super();
}
因此,在初始化子类的任何字段之前调用基类的构造函数。你的基类做了一些应该避免的事情:调用一个可覆盖的方法。
由于在子类中重写了此方法,因此它在尚未完全构造的对象上调用,因此将子类字段视为null。
答案 2 :(得分:0)
以下是伪C#/ Java中的多态性示例:
for (int i=0; i< hash[key].size(); i++) {
auto value = hash[key][i];
if (value.first == count) {
value.second.push_back(s);
inHash = true;
break;
}
}
Main函数不知道动物的类型,并且取决于MakeNoise()方法的特定实现的行为。
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 ());
}
出: A是1 A是2 B's 5
我的规则: 1.不要使用声明中的默认值进行初始化(null,false,0,0.0 ...)。 2.如果没有更改字段值的构造函数参数,则首选声明中的初始化。 3.如果由于构造函数参数而改变字段的值,则将初始化放在构造函数中。 4.在练习中保持一致。 (最重要的规则)
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);
}
}
或
public class Dice
{
private int topFace = 1;
private Random myRand = new Random();
public void Roll()
{
// ......
}
}