Why not abstract fields?
为什么Java类不能有抽象的字段,比如它们可以有抽象的方法?
例如:我有两个类扩展同一个抽象基类。这两个类中的每个类都有一个相同的方法,除了其中的字符串常量(恰好是一条错误消息)之外。如果字段可以是抽象的,我可以使这个常量成为抽象的,并将该方法拉到基类中。相反,我必须创建一个名为getErrMsg()的抽象方法,在本例中,它返回字符串,在两个派生类中重写该方法,然后我可以提取该方法(现在调用抽象方法)。
为什么我不能从抽象的领域开始呢?Java能被设计成允许这样吗?
- 听起来您可以通过使字段非常量并通过构造函数提供值来回避整个问题,最终得到一个类(而不是两个类)的两个实例。
- 通过在一个超级类中使一个字段成为抽象字段,您可以明确地指出每个子类都必须有这个字段,因此这与非抽象字段没有什么不同。
- @彼得,我不确定我是否明白你的意思。如果在抽象类中指定了非抽象常量,那么它的值在所有子类中也是常量。如果它是抽象的,那么它的值必须由每个子类实现/提供。所以,情况完全不一样。
- @Liltitus27我认为我3.5年前的观点是,拥有抽象字段不会改变太多,除非打破了将界面用户与实现分离的整个想法。
- 这将很有帮助,因为它可以在子类中允许自定义字段注释。
您可以通过在抽象类中有一个在其构造函数(未测试代码)中初始化的最终字段来完成所描述的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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,编译器将给出一个警告->strike>一个错误,就像没有实现抽象方法一样。
- 这里没有编辑可以尝试,但这也是我要发布的内容。
- "编译器将发出警告"。实际上,子构造函数将尝试使用不存在的noargs构造函数,这是一个编译错误(不是警告)。
- 在@stephen c的注释中添加"like when an abstract method is not implemented"也是一个错误,而不是警告。
- 好答案-这对我很有用。我知道已经有一段时间没有发布了,但我认为Base需要声明abstract。
- 它对于单元测试不起作用,因为它们必须用默认的构造函数初始化。
- 我仍然不喜欢这种方法(尽管这是唯一的方法),因为它为构造函数添加了一个额外的参数。在网络编程中,我喜欢生成消息类型,其中每个新消息实现一个抽象类型,但必须有一个唯一的指定字符,如acceptmessage的"a"和loginmessage的"l"。这些保证了一个定制的消息协议能够将这个区别字符放到线路上。这些都是隐式的类,因此它们最好作为字段使用,而不是传递给构造函数的内容。
我觉得没有意义。您可以将函数移动到抽象类,并只重写一些受保护的字段。我不知道这是否适用于常量,但效果是一样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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的实现/覆盖/任何内容?我以为你只是想在基类中使用这个方法,当时不知道如何处理这个字段。
- 那正是我打字的时候。这里只有快死的人…除了你在错误消息上拼错了"foo"。:)
- 感谢您指出:)
- 好吧,我当然可以。我想到了。但是,当我知道我要重写每个派生类中的值时,为什么必须在基类中设置一个值呢?您的参数还可以用来说明在允许抽象方法中也没有任何值。
- 这样不是每个子类都必须重写这个值。我也可以定义protected String errorMsg;,它以某种方式强制我在子类中设置值。它类似于那些将所有方法实际实现为占位符的抽象类,因此开发人员不必实现每个方法,尽管他们不需要。
- 嗯…除非我错了,否则不能在Java中重写字段,只能被遮蔽。所以getErrorMsg()总是返回"—这正是保罗想知道的原因。
- 说protected String errorMsg;并不强制您在子类中设置值。
- @梅里顿:是的,所以必须在构造函数中设置。
- @劳伦斯·冈萨尔夫斯:好吧,它的执行方式是,如果不是在子类中,那么errorMsg就是NULL。
- @保罗--"但是为什么我必须在基类中设置一个值…"。不必在基类中设置值。
- 如果值为空,如果不将其设置为"强制",那么您将如何称为"非强制"?
- @劳伦斯·冈萨尔维斯:在基类中给字段一个值。
- @费利克斯,执行和合同有区别。整个文章围绕抽象类展开,抽象类又代表了必须由任何子类实现的契约。所以,当我们讨论拥有一个抽象字段时,我们说任何子类必须实现该字段的值,每个契约。你的方法不能保证任何价值,也不能像合同那样明确地说明预期的价值。非常非常不同。
显然,它本来可以被设计成允许这样做,但在封面之下,它仍然需要进行动态调度,因此需要一个方法调用。Java的设计(至少在早期)在某种程度上是一种极简主义的尝试。也就是说,如果可以很容易地被语言中已有的其他特性模拟,那么设计者试图避免添加新特性。
- @劳伦斯-我并不清楚抽象字段可以包括在内。类似的事情可能会对类型系统和语言可用性产生深刻和根本的影响。(同样地,多重继承也带来了深层次的问题……这可能会伤害到不谨慎的C++程序员。
另一个选项是将字段定义为基类中的公共字段(如果愿意,最后定义),然后在基类的构造函数中初始化该字段,具体取决于当前使用的子类。它有点可疑,因为它引入了循环依赖。但是,至少它不是一个可以改变的依赖关系——也就是说,子类要么存在要么不存在,但是子类的方法或字段不能影响field的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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;
}
}
} |
读了你的标题,我以为你指的是抽象实例成员;我看不出他们有多大用处。但是抽象静态成员完全是另一回事。
我经常希望能在Java中声明如下方法:
1 2 3 4 5 6 7
| public abstract class MyClass {
public static abstract MyClass createInstance();
// more stuff...
} |
基本上,我希望坚持父类的具体实现提供带有特定签名的静态工厂方法。这将使我能够使用Class.forName()获得一个具体类的引用,并确保我可以在自己选择的约定中构造一个具体类。
- 是的…这可能意味着你使用的反射远远超过很多!!
- 我觉得这是个奇怪的编程习惯。class.forname(..)闻起来像是设计缺陷。
- 现在,我们基本上总是使用SpringDI来完成这类工作;但是在该框架变得众所周知和流行之前,我们将在属性或XML文件中指定实现类,然后使用class.forname()来加载它们。
- 我可以给你一个使用案例。缺少"抽象字段"几乎不可能在抽象泛型类中声明泛型类型的字段:@autowired T fieldName。如果T被定义为类似于T extends AbstractController的类型,类型擦除会导致字段生成为AbstractController类型,并且不能自动连线,因为它不是具体的,也不是唯一的。我一般认为它们没有多大用处,因此它们不属于语言。我不同意的是他们没有用。