为什么不抽象字段?


99

为什么Java类不能像抽象类那样具有抽象字段?

例如:我有两个扩展同一个抽象基类的类。这两个类各自具有相同的方法,但其中包含String常数(恰好是错误消息)。如果字段可以是抽象的,那么我可以使这个常量抽象,并将该方法引入基类。相反,我必须创建一个抽象方法(getErrMsg()在这种情况下称为该方法),该方法返回String,在两个派生类中重写此方法,然后可以拉出该方法(现在称为抽象方法)。

为什么我不能仅将字段抽象化?Java是否可以设计成允许这样做?


3
听起来您可以通过使字段变为非恒定字段并仅通过构造函数提供值来避开整个问题,最后以一个类的2个实例而不是2个类结束。
内特2010年

5
通过在超类中使字段抽象化,您可以明确每个子类都必须具有该字段,因此这与非抽象字段没有什么不同。
彼得·劳瑞

@彼得,我不确定我是否遵循您的观点。如果在抽象类中指定了非抽象常量,那么它的值在所有子类中也都是常量。如果它是抽象的,则每个子类都必须实现/提供它的值。因此,根本不会是一样的。
liltitus13年

1
@ liltitus27我想我3.5年前的观点是,拥有抽象字段不会有太大变化,除非打破了将接口用户与实现分开的整个想法。
彼得·劳瑞

这将是有帮助的,因为它可能允许子类中的自定义字段注释
geg

Answers:


102

您可以通过在抽象类中的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";
    }
}

如果您的子类“忘记”通过超级构造函数初始化最后一个,则编译器将发出警告错误,就像未实现抽象方法时一样。


还没有得到一个编辑在这里尝试一下,但是这是我将要发布以及..
蒂姆

6
“编译器将发出警告”。实际上,Child构造函数将尝试使用不存在的noargs构造函数,这是编译错误(不是警告)。
Stephen C 2010年

在@Stephen C的注释中添加“就像未实现抽象方法时一样”也是错误,而不是警告。
劳伦斯·贡萨尔维斯

4
好的答案-这对我有用。我知道它已经发布了一段时间,但认为Base需要声明abstract
史蒂夫·钱伯斯

2
我仍然不喜欢这种方法(即使这是唯一的方法),因为它为构造函数添加了一个额外的参数。在网络编程中,我喜欢创建消息类型,其中每个新消息都实现一种抽象类型,但必须具有唯一的指定字符,例如AcceptMessage的'A'和LoginMessage的'L'。这些确保自定义消息协议将能够将该区别字符放在线路上。这些对于类是隐式的,因此最好将它们用作字段,而不是传递给构造函数的内容。
亚当·休斯

7

我认为没有任何意义。您可以将函数移至抽象类,并覆盖某些受保护的字段。我不知道这是否适用于常量,但是效果是一样的:

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在子类中实施/覆盖/执行什么?我以为您只是想在基类中使用该方法,然后不知道该如何处理该字段。


4
好吧,我当然可以做到。我已经想到了。但是,当我知道我将在每个派生类中覆盖该值时,为什么必须在基类中设置一个值?您的论点也可以用来争论允许抽象方法也没有任何价值。
Paul Reiners 2010年

1
这样,并不是每个子类必须重写该值。我还可以定义protected String errorMsg;哪些以某种方式强制设置子类中的值。它类似于实际上将所有方法实现为占位符的抽象类,以便开发人员尽管不需要每个方法,也不必实现每个方法。
Felix Kling'2

7
protected String errorMsg;不会强制您设置在子类中的价值。
劳伦斯·贡萨尔维斯

1
如果不设置该值为null,则将其计为“执行”,那么您将其称为“非执行”吗?
劳伦斯·贡萨尔维斯

9
@felix,执行合同之间有区别。整个文章以抽象类为中心,抽象类又代表了任何子类都必须履行的合同。因此,当我们谈论拥有抽象字段时,我们是说任何子类都必须根据合同实现该字段的值。您的方法不能保证任何价值,也不能像合同那样明确说明预期的结果。非常非常不同
liltitus13年

3

显然,它本来可以允许这样做的,但是在幕后,它仍然必须进行动态分配,因此需要进行方法调用。Java的设计(至少在早期)是在某种程度上试图做到极简。就是说,如果语言中已经存在的其他功能可以轻松地模拟它们,那么设计人员将避免添加新功能。


@Laurence -这是不是明显,我认为抽象的领域可能已经包括在内。诸如此类的事情可能会对类型系统和语言可用性产生深远的根本影响。(以相同的方式,多重继承会带来深层次的问题……可能会咬住粗心的C ++程序员。)
Stephen C 2010年

0

阅读标题,我认为您是指抽象实例成员。我看不出它们有什么用。但是抽象静态成员完全是另一回事。

我经常希望我可以在Java中声明一个类似于以下内容的方法:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

基本上,我想坚持认为,父类的具体实现提供了带有特定签名的静态工厂方法。这将使我可以引用一个具体的类,Class.forName()并确定可以按照自己的选择构造一个类。


1
是的...这可能意味着您正在使用反射功能!!
斯蒂芬·C

听起来对我来说是个奇怪的编程习惯。Class.forName(..)闻起来像是设计缺陷。
whiskeysierra

这些天来,我们基本上总是使用Spring DI来完成这种事情。但是在该框架流行起来之前,我们将在属性或XML文件中指定实现类,然后使用Class.forName()加载它们。
德鲁·威尔斯

我可以给他们一个用例。缺少“抽象字段”几乎不可能在抽象的泛型类中声明泛型的字段 @autowired T fieldName。如果将if T定义为T extends AbstractController,则类型擦除会导致该字段作为type生成,AbstractController并且不能自动连接,因为它不是具体的也不是唯一的。我通常同意它们没有太多用处,因此它们不属于该语言。我不同意的是它们没有用。
DaBlick

0

另一种选择是在基类中将该字段定义为公共字段(如果需要,可以将其定义为final),然后根据当前使用的子类在基类的构造函数中初始化该字段。这有点阴暗,因为它引入了循环依赖。但是,至少它不是一个可以改变的依赖项-即,子类将存在或不存在,但是子类的方法或字段不会影响的值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;
    }
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.