如何创建“抽象领域”?

我知道java中不存在抽象字段。 我也读过这个问题,但提出的解决方案并不能解决我的问题。 也许没有解决方案,但值得问:)

问题

我有一个抽象类,它根据其中一个字段的值在构造函数中执行操作。 问题是该字段值将根据子类而改变 。 我该怎么做才能对子类重新定义的字段的值进行操作?

如果我只是“覆盖”子类中的字段,则对抽象类中字段的值进行操作。

我对任何可以确保在子类实例化期间完成操作的解决方案持开放态度即将操作放在构造函数中每个子类调用的方法中都不是有效的解决方案,因为有人可能会扩展抽象类并忘记调用方法)。

另外,我不想将字段的值赋予构造函数的参数。

有没有解决办法,或者我应该改变我的设计?


编辑:

我的子类实际上是我的主程序使用的一些工具,因此构造函数必须是公共的并且完全采用它们将被调用的参数:

tools[0]=new Hand(this); tools[1]=new Pencil(this); tools[2]=new AddObject(this); 

(子类是Hand,Pencil和AddObject,它们都扩展了抽象类Tool)

这就是为什么我不想改变构造函数。

我即将使用的解决方案是将上面的代码稍微更改为:

 tools[0]=new Hand(this); tools[0].init(); tools[1]=new Pencil(this); tools[1].init(); tools[2]=new AddObject(this); tools[2].init(); 

并使用抽象的getter访问该字段。

对于字段的抽象getter / setter怎么样?

 abstract class AbstractSuper { public AbstractSuper() { if (getFldName().equals("abc")) { //.... } } abstract public void setFldName(); abstract public String getFldName(); } class Sub extends AbstractSuper { @Override public void setFldName() { ///.... } @Override public String getFldName() { return "def"; } } 

另外,我不想将字段的值赋予构造函数的参数。

为什么不? 这是完美的解决方案。 使构造函数protected并且不提供默认构造函数,并且子类实现者被强制在其构造函数中提供一个值 – 它可以是公共的并将常量值传递给超类,使得参数对子类的用户不可见。

 public abstract class Tool{ protected int id; protected Main main; protected Tool(int id, Main main) { this.id = id; this.main = main; } } public class Pencil{ public static final int PENCIL_ID = 2; public Pencil(Main main) { super(PENCIL_ID, main); } } 

如何使用模板模式?

 public abstract class Template { private String field; public void Template() { field = init(); } abstract String init(); } 

通过这种方式,您可以强制所有子类实现init()方法,由于构造函数调用该方法,因此将为您分配字段。

您不能在构造函数中执行此操作,因为超类将在子类中的任何内容之前初始化。 因此,访问特定于子类的值将在超级构造函数中失败。
考虑使用工厂方法来创建对象。 例如:

 private MyClass() { super() } private void init() { // do something with the field } public static MyClass create() { MyClass result = new MyClass(); result.init(); return result; } 

您在此特定示例中遇到问题,其中MyClass无法进行子类化,但您可以使构造函数受到保护。 确保您的基类也具有此代码的公共/受保护构造函数。 这只是为了说明你可能需要两步初始化你想做什么。

您可以使用的另一个可能的解决方案是使用Factory类创建此抽象类的所有变体,您可以将该字段传递给构造函数。 您的工厂将是唯一了解该领域的工厂,工厂的用户可能会忘记它。

编辑:即使没有工厂,您也可以使您的抽象基类需要构造函数中的字段,因此所有子类在实例化时都必须向其传递值。

另外,我不想将字段的值赋予构造函数的参数。

有没有解决办法,或者我应该改变我的设计?

是的,我认为你应该改变你的设计,以便子类将值传递给构造函数。 由于在超类构造函数返回之前对象的子类部分才初始化,因此实际上没有其他干净的方法。 当然,这个工作:

 class Super { protected abstract int abstractField(); protected Super() { System.out.println("Abstract field: " + abstractField); } } class Sub { protected int abstractField(){ return 1337; } } 

…因为abstractField()实现不对对象状态进行操作。 但是,你不能保证子类不会认为更动态一点是个好主意,让abstractField()返回一个非常量值:

 class Sub2 { private int value = 5; protected int abstractField(){ return value; } public void setValue(int v){ value = v; } } class Sub3 { private final int value; public Sub3(int v){ value = v; } protected int abstractField(){ return value; } } 

这不符合您的期望,因为子类的初始化器和构造器运行在超类的初始化器和构造器之后。 new Sub2()new Sub3(42)都将打印Abstract field: 0因为在调用abstractField()时尚未初始化value字段。

将值传递给构造函数还有一个额外的好处,即存储值的字段可以是final

如果值是由子类的类型决定的,为什么你需要一个字段呢? 您可以使用一个简单的抽象方法来实现为每个子类返回不同的值。

我认为你需要一个可以对该参数起作用的工厂(也称为“虚拟构造函数”)。

如果用给定的语言很难做到,你可能会错误地思考它。

如果我理解正确:您希望抽象类的构造函数根据抽象类中的字段执行某些操作,但是由子类设置(希望如此)?

如果我弄错了你可以停止阅读……

但如果我做对了,那么你就是在尝试做一些不可能的事情。 类的字段以词法顺序实例化(因此,如果您声明字段“下方”或“之后”,则构造函数然后在调用构造函数之前不会实例化这些字段)。 另外,在对子类做任何事情之前,JVM运行整个超类(这就是为什么子类的构造函数中的“super()”调用需要是构造函数中的第一条指令…因为这仅仅是“建议”关于如何运行超类的构造函数的JVM)。

因此,只有在完全实例化超类之后,子类才开始实例化(并且超类的构造函数已经返回)。

这就是为什么你不能有抽象字段的原因:抽象类中不存在抽象字段(但只存在于子类中),因此对超级(抽象)类严重(!)“禁止”…因为JVM无法绑定对该字段的任何引用(因为它不存在)。

希望这可以帮助。