我正在查看游戏中的一些代码,我遇到了一些我以前从未见过的东西,我真的不知道发生了什么。
public abstract class Entity
{
public Entity(World world)
{
// irrelevent code
entityInit();
}
protected abstract void entityInit();
}
这里发生了什么?当它呼叫entityInit()
时会发生什么?
答案 0 :(得分:7)
从不实例化抽象类。只能实例化其具体的子类。因此,当调用具体子类(让我们称之为Foo
)构造函数时,它会调用super(world)
。然后,实体构造函数调用entityInit()
,该Foo
已被entityInit
覆盖。因此它调用了Foo entityInit
具体方法。
请注意,这是不好的做法,因为{{1}}方法将在尚未完全构造的对象上调用。因此,子类必须确保此方法不会访问它可能声明的任何字段,因为它们都将被整体化。
答案 1 :(得分:3)
会发生什么事情,调用具体子类的entityInit()
实现。由于Entity
是抽象的,因此只能从具体子类的构造函数中调用其构造函数,该构造函数必须具有entityInit()
的实现。
从abstact类的代码中调用抽象方法实际上是非常常见的,而且几乎是抽象方法的全部要点。另请参阅template method pattern。
但是,在这种情况下从构造函数调用抽象方法是problematic and should be avoided,主要是因为抽象方法将在未完全初始化的对象上运行,因此可能是不一致的州。具体来说,entityInit()
的实现将在它定义的类的构造函数之前运行。
答案 2 :(得分:2)
嗯,大多数情况下,这用于Template Method pattern。子类Entity
的非抽象类将实现entityInit()
方法。这些子类实现了必须为该类定义entityInit()
的方式。
在维基百科中,它说......
在面向对象的编程中,首先创建一个提供的类 算法设计的基本步骤。这些步骤已实施 使用抽象方法。稍后,子类会更改摘要 实现实际行动的方法。因此保存了通用算法 在一个地方,但具体步骤可能会被子类改变。
在您的情况下,您不必担心子类必须entityInit()
本身,因为构造函数默认会这样做:
示例:
public class StrictEntity extends Entity {
public StrictEntity(World world) {
super(world); //This will call entityInit();
}
protected void entityInit() {
//Example, don't take it as genuine.
PropertyConfig.getInstance.setStrict(true);
}
}
答案 3 :(得分:1)
嗯,没什么。
除非具体类实现抽象方法entityInit
,否则您将无法创建将使用该方法的Entity
类。
答案 4 :(得分:1)
因为你需要创建一个具体的子类,你还需要实现entityInit()
。然后将调用该方法。
您无法创建抽象类的实例,具体类(您可以创建实例)必须没有抽象方法。所以一切都很好。
注意:请注意,如果访问子类中定义的字段,则在entityInit()
中访问它们可能会导致NullPointerException,因为它们可能未初始化。
示例(基于您的班级):
class Person extens Entity {
private String name = "Player";
protected void entityInit() {
int nameLen = name.length(); //NPE here!!!
}
}
虽然这个例子没有太大的逻辑意义,但它应该说明一点。
首先调用Entity
构造函数,然后调用entityInit()
。但是,由于Person
的初始化程序块尚未运行,name
仍然为空。
答案 5 :(得分:1)
这是一种常见的做法。您在顶级方法中使用抽象方法,实现者只需要实现抽象,因此逻辑将保留在基类中。
很抱歉没有注意到它是一个构造函数......在那个位置上很奇怪......
答案 6 :(得分:0)
不要从构造函数中调用抽象/虚方法。它会调用具体的子类实现。但具体子类的成员变量尚未初始化。
另一种说法不要让你的这个从构造函数体中逃脱的方法。
但在这种特殊情况下,该方法的整个目的是初始化this
对象的成员。所以没关系。但是使用这种方法,我们必须链接超类的方法调用(如果它有实现)。