为啥不抽象字段?
Posted
技术标签:
【中文标题】为啥不抽象字段?【英文标题】:Why not abstract fields?为什么不抽象字段? 【发布时间】:2011-01-13 17:41:58 【问题描述】:为什么 Java 类不能有抽象字段,就像它们可以有抽象方法一样?
例如:我有两个扩展同一个抽象基类的类。这两个类都有一个相同的方法,除了一个字符串常量,这恰好是一个错误消息,在它们里面。如果字段可以是抽象的,我可以使这个常量抽象并将方法拉到基类中。相反,我必须创建一个抽象方法,在这种情况下称为 getErrMsg()
,它返回字符串,在两个派生类中重写此方法,然后我可以调用该方法(现在调用抽象方法)。
为什么我不能一开始就将字段抽象化? Java 的设计是否允许这样做?
【问题讨论】:
听起来你可以回避整个问题,方法是使字段成为非常量并简单地通过构造函数提供值,最终得到一个类的 2 个实例而不是 2 个类。 通过在超类中抽象化字段,您可以明确每个子类都必须具有该字段,因此这与非抽象字段没有什么不同。 @peter,我不确定我是否遵循您的观点。如果在抽象类中指定了非抽象常量,那么它的值在所有子类中也是常量。如果它是抽象的,那么它的值将必须由每个子类实现/提供。所以,根本就不一样。 @liltitus27 我认为我在 3.5 年前的观点是,拥有抽象字段不会有太大变化,除非打破了将界面用户与实现分离的整个想法。 这会很有帮助,因为它可以允许子类中的自定义字段注释 【参考方案1】:另一种选择是在基类中将字段定义为公共(如果您愿意的话),然后在基类的构造函数中初始化该字段,具体取决于当前正在使用的子类。这有点可疑,因为它引入了循环依赖。但是,至少它不是一个可以改变的依赖——即,子类将存在或不存在,但子类的方法或字段不能影响field
的值。
public abstract class Base
public final int field;
public Base()
if (this instanceof SubClassOne)
field = 1;
else if (this instanceof SubClassTwo)
field = 2;
else
// assertion, thrown exception, set to -1, whatever you want to do
// to trigger an error
field = -1;
【讨论】:
【参考方案2】:您可以通过在抽象类中有一个在其构造函数中初始化的 final 字段(未经测试的代码)来执行您所描述的操作:
abstract class Base
final String errMsg;
Base(String msg)
errMsg = msg;
abstract String doSomething();
class Sub extends Base
Sub()
super("Sub message");
String doSomething()
return errMsg + " from something";
如果您的子类“忘记”通过超级构造函数初始化final,编译器将给出警告一个错误,就像没有实现抽象方法时一样。
【讨论】:
还没有编辑在这里尝试,但这也是我要发布的内容.. “编译器会给出警告”。实际上, Child 构造函数会尝试使用不存在的 noargs 构造函数,这是一个编译错误(不是警告)。 并添加到@Stephen C 的评论中,“就像没有实现抽象方法一样”也是错误,而不是警告。 好答案 - 这对我有用。我知道它发布已经有一段时间了,但我认为Base
需要声明为 abstract
。
我仍然不喜欢这种方法(尽管它是唯一的方法),因为它向构造函数添加了一个额外的参数。在网络编程中,我喜欢创建消息类型,其中每个新消息都实现一个抽象类型,但必须具有唯一的指定字符,例如 AcceptMessage 的“A”和 LoginMessage 的“L”。这些确保自定义消息协议能够将这个区别字符放在网络上。这些对类来说是隐含的,因此最好将它们用作字段,而不是传递给构造函数的东西。【参考方案3】:
我认为这没有意义。您可以将函数移动到抽象类并覆盖一些受保护的字段。我不知道这是否适用于常量,但效果是一样的:
public abstract class Abstract
protected String errorMsg = "";
public String getErrMsg()
return this.errorMsg;
public class Foo extends Abstract
public Foo()
this.errorMsg = "Foo";
public class Bar extends Abstract
public Bar()
this.errorMsg = "Bar";
所以你的意思是你想在子类中强制执行/覆盖/errorMsg
的任何内容?我以为你只是想在基类中有方法,然后不知道如何处理该字段。
【讨论】:
好吧,我当然可以这样做。我已经想到了。但是,当我知道我将在每个派生类中覆盖该值时,为什么我必须在基类中设置一个值?你的论点也可以用来论证允许抽象方法也没有任何价值。 好吧,这样并不是每个子类都来覆盖这个值。我也可以只定义protected String errorMsg;
,它以某种方式强制我在子类中设置值。它类似于那些将所有方法实际实现为占位符的抽象类,因此开发人员不必实现每个方法,尽管他们不需要它。
说protected String errorMsg;
并不强制您在子类中设置值。
如果你不设置它的值为 null 算作“强制执行”,那你称什么为不强制执行?
@felix,enforcement 和 contracts 是有区别的。整篇文章以抽象类为中心,抽象类又代表了任何子类都必须履行的契约。因此,当我们谈论拥有一个抽象字段时,我们是在说任何子类必须根据合同实现该字段的值。您的方法不保证任何价值,也不像合同那样明确说明预期的内容。非常非常不同。【参考方案4】:
阅读您的标题,我以为您指的是抽象实例成员;我看不出它们有多大用处。但是抽象的静态成员完全是另一回事。
我经常希望我可以在 Java 中声明如下方法:
public abstract class MyClass
public static abstract MyClass createInstance();
// more stuff...
基本上,我想坚持我的父类的具体实现提供了一个带有特定签名的静态工厂方法。这将允许我使用Class.forName()
获得对具体类的引用,并确保我可以按照自己选择的约定构造一个。
【讨论】:
是的...这可能意味着您使用反射的次数太多了! 对我来说听起来像是一个奇怪的编程习惯。 Class.forName(..) 闻起来像一个设计缺陷。 这些天我们基本上都是用Spring DI来完成这种事情;但在该框架变得广为人知和流行之前,我们会在属性或 XML 文件中指定实现类,然后使用 Class.forName() 来加载它们。 我可以给你一个他们的用例。没有“抽象字段”几乎不可能在抽象泛型类中声明泛型类型的字段:@autowired T fieldName
。如果T
被定义为T extends AbstractController
之类的东西,类型擦除会导致字段生成为AbstractController
类型,并且不能自动装配,因为它不是具体的并且不是唯一的。我通常同意它们没有太多用处,因此它们不属于该语言。我不同意的是它们没有用处。【参考方案5】:
显然它可以被设计为允许这样做,但在幕后它仍然必须进行动态调度,因此需要进行方法调用。 Java 的设计(至少在早期)在某种程度上是一种极简主义的尝试。也就是说,如果新功能可以很容易地被语言中已有的其他功能模拟,设计人员会尽量避免添加新功能。
【讨论】:
@Laurence - 对我来说不显然可以包含抽象字段。类似的事情可能会对类型系统和语言可用性产生深远而根本的影响。 (就像多重继承带来了深层次的问题......可能会让粗心的 C++ 程序员陷入困境。)以上是关于为啥不抽象字段?的主要内容,如果未能解决你的问题,请参考以下文章