为什么接口不能有字段?

5

请忽略第二次编辑之前的所有内容。

我正在尝试做类似于这样的事情:

public interface IModifier{
   public String nameTag;
   public void foo();
}

我尝试这样做的原因是:我有一个类SpecificModifier实现了IModifier接口,还有许多非常相似的类也实现了IModifier接口。我希望每个实现IModifier接口的类都有一个公共的String nameTag。
编辑:我已经确认不能这样做,但是有人能解释为什么接口不能要求一个字段吗?
第二次编辑:
我对抽象类和接口的作用有所了解。接口仅用于声明实现它的所有对象的必要部分,以便可以引用所有对象的公共部分。而抽象类用于为多个类提供公共功能。
这有点过于简化,但无论如何,除了语言设计者的疏忽外,我仍然看不到接口不能有抽象字段的理由。
有人能提供一个原因吗?
7个回答

4

接口规定了实现接口的具体类必须遵守的合同。该合同描述了实现应该如何运作。在接口说明中,应有清晰的注释,描述每个方法的目的。使用接口可以将合同与实际实现分离。字段是实现的细节,因为字段不能描述一个类应该如何“行动”。

例如,接口通常用作声明类型,而具体实现则用作实际类型。

    Map<Key,Value> m = new HashMap<>();

考虑一下java.util.Map接口。通过其一系列方法描述了Map应如何运作。有几种不同的Map接口实现允许用户选择适合自己需求的实现。

指定一个字段必须被几个子类使用意味着存在某种类层次结构。在这种情况下,抽象类可以解决问题。

   abstract class IModParent implements IModifier{
      protected String nameTag;
   }

现在您可以拥有一个具体类。
   class SpecificModifier extends IModParent{

      SpecificModifier(String nameTag){ this.nameTag = nameTag; }

      @Override
      public void foo(){ System.out.println(nameTag); }
   }

还有一个声明。

    IModifier imod = new SpecificModifier("MyName");

这样做可以让你在使用接口类型的同时,通过一个不可实例化的抽象类来共享具体类的实现细节,从而使得一组具体类更加灵活。

我感谢您的解释,我现在明白了。 - UberAffe

3
很遗憾,Java中的接口只能包含方法和常量。但是,还有一种替代方案。添加一个像这样的方法:
String getNameTag();

你看,这样实现就必须包含一个nameTag字段,或者可以做一些其他的事情来返回一个字符串。

另外,据我所知,你不需要在接口方法中添加访问修饰符。


如果我没记错的话,访问修饰符不仅是可选的,而且除了 public 以外使用任何其他修饰符都会报错。 - Ian McLaird

2

接口用于方法和常量。

根据您的需求,您需要使用抽象类

public abstract class IModifier{
   public String nameTag;
   public abstract void foo();
}

现在回答你的问题,为什么接口无法要求字段? 答案是:这是抽象类的特性。接口和抽象类之间几乎没有区别。 我希望我已经解答了你的棘手问题。

1
从设计的角度来看: 接口定义了一份合意行为的契约,也就是可以完成的方法(例如 int getAge() ),而不是如何完成它。 然后是实例字段(int age),这更多地是需要实现该行为所需的部分,但并不自然。而静态 final 字段(例如 static final int CENTURIONAGE=100)则不是特定于实现的,仍然可以在接口上使用。 然后,在达成协议后,如果您要进入行为实现,可以转到类和抽象类等。

0

如果您查阅Java文档,您将从中获得实际的语句。

抽象类与接口类似。您无法实例化它们,并且它们可能包含一些声明了实现或未声明实现的方法。但是,使用抽象类,您可以声明非静态和非final字段,并定义公共、受保护和私有的具体方法。对于接口,所有字段都自动为public、static和final,并且您声明或定义(作为默认方法)的所有方法都是public的。此外,您只能扩展一个类,无论它是否是抽象的,而您可以实现任意数量的接口。

在使用抽象类或接口时应该如何选择?

如果以下任何语句适用于您的情况,请考虑使用抽象类:

  1. 您想在几个密切相关的类之间共享代码。
  2. 您预计扩展您的抽象类的类具有许多共同的方法或字段,或需要除public之外的访问修饰符(例如protected和private)。
  3. 您想声明非静态或非final字段。这使您能够定义可以访问和修改它们所属对象状态的方法。

如果以下任何语句适用于您的情况,请考虑使用接口:

1. 你期望无关的类实现你的接口。例如,许多无关的类都实现了Comparable和Cloneable接口。

2. 你想要指定特定数据类型的行为,但不关心谁来实现它的行为。

3. 你想要利用类型的多重继承。

然而,根据你的第一个问题编辑,你可以在接口中定义一个字段,但有一个注意事项。它必须是publicstaticfinal的。换句话说,只能定义常量 ;)

public interface TimeClient {

public static final String TIME_ZONE = "Europe/Amsterdam";    //Defines a static field     

static public ZoneId getZoneId(String zoneString) {
    try {
        return ZoneId.of(zoneString);
    } catch (DateTimeException e) {
        System.err.println("Invalid time zone: " + zoneString
                + "; using default time zone instead.");
        return ZoneId.systemDefault();
    }
}

//Defines a default method
default public ZonedDateTime getZonedDateTime(String zoneString) {
    return ZonedDateTime.of(LocalDateTime.MAX, getZoneId(zoneString));
}}

上述代码将会编译通过。


0

在许多项目中,我所做的是定义一个“接口”,该接口不仅定义了字段,还定义了字段的“契约”。

此前在这个主题中提出的使用abstract基类的建议有一个严重的限制。在Java的有限形式的多重继承中,具体类可以实现多个接口,但是...它只能extend一个基类。这意味着基类方法仅适用于需要字段契约的一个接口。

以用例的方式来看,假设我想要为几种领域对象定义合同/接口:

  • 带有到期日期的对象
  • 带有审计跟踪(由谁创建,创建日期等)的对象

我可能有一些对象可以过期,但不需要审计跟踪。我可能有其他对象则相反:它们需要审计跟踪,但没有到期日期。

基类不允许在其中进行“挑选和选择”。任何基类都必须同时定义两者,或者您必须拥有所有组合的基类。

这是我的解决方案,它已经运作得非常好:我在接口中将setter和getter定义为抽象方法。您将实现留给具体类。

public interface Auditable<USER_TYPE> {
    Instant getCreatedOn();
    void setCreatedOn(Instant)

    USER_TYPE getCreatedBy();
    void setCreatedBy(USER_TYPE creator);

一个同时具有 Audtiable(使用 String 来标识用户)和 Expirable 特性的具体类应该长这样(我使用了 Lombok)。
@Getter @Setter
public AuditableExpirableObject implement Auditable<String>, Expirable {
    private String createdBy
    private Instant createdOn;
    private Instant expirationDate;

这使得具体对象可以选择使用哪些接口。您确实需要定义字段,但通常在具体类型中定义它们并实现接口的契约是一件“好事”。一个例子是允许每个具体类确定以下内容:

  • 数据类型(如Auditable的用户类型)
  • 用于持久化或序列化的注释(Hibernate、JPA、Jackson、Gson等)
  • 自定义初始化或保护级别

0

不行。

接口只能要求方法,而不能要求字段(或构造函数)。

您可以通过在接口中放置getter和/或setter方法来实现相同的效果。


这是我根据其他问题的阅读得出的结论,我主要是在寻求确认,并看看是否有人能够解释为什么会这样。 - UberAffe
“为什么?”这类问题总是很棘手。这里有更多的讨论:https://dev59.com/TnI_5IYBdhLWcg3wJPZu - Thilo
这实际上是我之前一直在关注的一个问题,但所有字段必须是静态常量的原因都是因为您无法直接实例化接口,而有关在抽象类中使用抽象变量的评论被忽略了。 - UberAffe
抽象类仍然是一个抽象类。它成为子类实现的一部分,可以通过这种方式贡献一个字段。而接口则不行。我的猜测是,如果有技术原因(而不仅仅是设计考虑),那么解析字段和方法的过程基本上是不同的,因此如果在编译时不知道实现类,则无法进行字段解析(对于字段,目前已知实现类,但如果在接口中,则不会)。但我只是猜测。 - Thilo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接