【问题标题】:Why can't an interface have fields?为什么接口不能有字段?
【发布时间】:2015-08-26 00:12:14
【问题描述】:

在第二次编辑之前忽略所有内容

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

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

我尝试这样做的原因是: 我有一个类 SpecificModifier 实现 IModifier 并且有许多非常相似的类也实现了 IModifier。 我希望每个实现 IModifier 的类都有一个公共字符串 nameTag。

编辑:我已经确认我不能这样做,但是有人可以解释为什么接口不能需要字段吗?

编辑两个:

我对抽象类与接口的目的的理解。接口纯粹用于声明实现它的任何东西的必要部分,以便所有对象都有可以引用的公共部分。而抽象类用于为多个类提供通用功能。

这有点过于简单化了,但无论如何,除了语言设计者的疏忽外,我仍然认为没有任何理由接口不能具有抽象字段。

谁能说明原因?

【问题讨论】:

    标签: java interface


    【解决方案1】:

    接口指定了一个契约,实现该接口的具体类必须遵守该契约。这个契约描述了一个实现应该如何行动。在接口规范中,应该有明确的 cmets 来描述每个方法的目的是什么。接口的使用将合同与实际实现分离。字段是一个实现细节,因为字段不描述一个类应该如何“充当”。

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

        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");
    

    这为您提供了使用接口类型的灵活性,同时仍然能够通过您想要的一组具体类之间的不可实例化抽象类共享实现细节。

    【讨论】:

    • 感谢您的阐述,我想我现在明白了。
    【解决方案2】:

    不,可悲的是你不能。 java中的接口只能包含方法和常量。 但是,还有一个替代方案。添加这样的方法:

    String getNameTag();
    

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

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

    【讨论】:

    • IIRC,不仅访问修饰符是可选的,使用public以外的任何东西都是错误的。
    【解决方案3】:

    接口用于方法和常量。

    您需要根据自己的要求使用abstract class

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

    现在回答您的问题WHY an interface cannot require a field? Ans: 因为那是abstract class 的特点。 interfaceabstract class 之间几乎没有区别。

    希望我回答了你的棘手问题。

    【讨论】:

      【解决方案4】:

      从设计的角度: 接口定义了一个约定行为的契约,这是可以做的,也就是方法。 (例如 int getAge() )以及如何做到这一点。 然后实例字段(int age)更多地是您实现行为所需的部分,并不自然适合。非实现特定的静态最终字段(例如 static final int CENTURIONAGE=100)在接口上仍然可用。

      然后在同意合同之后,如果你去行为实现你继续去类和抽象类等。

      【讨论】:

        【解决方案5】:

        没有。

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

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

        【讨论】:

        • 这就是我根据我阅读的其他问题得出的结论,我主要是在寻找确认,看看是否有人可以解释为什么会这样。
        • “为什么?”问题总是很棘手。这里还有一些讨论:stackoverflow.com/questions/1513520/…
        • 这实际上是我之前一直在看的一个,但是字段必须是静态最终的所有原因都说这是因为你不能直接实例化接口,并且有一条关于能够使用的评论抽象类中的抽象变量被忽略。\
        • 抽象类仍然是抽象类。它成为子类实现的一部分,并且可以通过这种方式贡献一个字段。而接口则没有。我的猜测是,如果完全有技术原因(而不仅仅是设计考虑),可能是字段和方法的解析过程根本不同,因此如果不知道实现类,则无法完成字段解析编译时(它目前用于字段,但如果它在接口中则不会)。但我只是猜测。
        【解决方案6】:

        如果你查看 java 文档,你会从那里得到实际的语句。

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

        你应该使用哪个,抽象类还是接口?

        如果这些陈述中的任何一个适用于您的情况,请考虑使用抽象类:

        1. 您希望在几个密切相关的类之间共享代码。
        2. 您希望扩展抽象类的类有许多共同点 方法或字段,或需要公共以外的访问修饰符(例如受保护和私有)。
        3. 您要声明非静态或非最终字段。这使您能够定义可以访问和修改它们所属对象状态的方法。

        如果这些陈述中的任何一个适用于您的情况,请考虑使用接口:

        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));
        }}
        

        上面的代码会编译。

        【讨论】:

          【解决方案7】:

          这是我在许多项目中所做的,以便能够定义一个“接口”,它定义的不是字段而是字段的“合同”。

          本主题前面提出的使用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 等)
          • 自定义初始化或保护级别

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-01-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-09-09
            • 1970-01-01
            相关资源
            最近更新 更多