【问题标题】:Protected in Interfaces受接口保护
【发布时间】:2011-07-19 14:49:15
【问题描述】:

为什么interface 定义中的所有方法都隐含public?为什么它不允许protected 方法?

【问题讨论】:

  • 非常好的问题。对于 Java 中的几乎所有其他事情,我都找到了做出选择的真正原因,但对于这个我没有。对我来说,在接口中定义一个受保护的方法是非常有意义的,它允许同一个包中的另一个类在实现对象上使用该方法,而无需公开该方法,除了打包成员,发往世界其他地方。
  • @MarkusA。但是接口可以双向工作,即它们也可以由当前包之外的类实现(然后可能作为参数传递给该包内的方法)。当前包之外的类如何能够实现某些公共接口的“受保护”方法?
  • @MartinStettner:不会。这就是重点。一个包可能有多个实现一个接口的不相关类,并希望保证任何接收该接口类型引用的代码都会以某种方式运行。如果可以防止外部代码声称实现接口,同时以违背其合同的方式行事,则这种保证可以变得更加强大。
  • @MarkusA。你提出了一个很好的观点,你应该可以通过Java 9's module system实现它
  • 如果接口有protected方法,所有实现类都将被视为接口的子类型。并且所有这些类都可以访问受保护的方法。它不会使方法上的protected 关键字无用吗?只要我们没有任何方法来限制谁实现了这个接口,方法上受保护的关键字是没有用的。如果我错了,请纠正我!

标签: java interface


【解决方案1】:

因为接口应该意味着“你可以从课堂之外看到的东西”。添加非公共方法是没有意义的。

【讨论】:

  • 但是为什么不具有只有与接口相同包的成员才能“从类外部看到”的功能呢?我有几个我希望这样做的用例。
  • @MarkusA。我意识到这已经很晚了,但是您可以创建一个完整的abstract class,这就是interfaces 的全部内容,并指定您想要的任何访问权限。当然这失去了interfaces 在 Java 中获得的多个实现的好处,但是老实说,建立一个遵守其他一些包限制的合同将是不可测试和令人困惑的,因为您实际上无法在外部访问您自己的实现方法那个包。
  • @pickypg 但是如果实现接口的类已经扩展了另一个类,你不能让它扩展另一个类。对于仅在包内使用的接口,我不会感到困惑。
  • @Raveline, -1, 这就引出了一个问题“为什么接口应该意味着你可以从课堂外看到的东西?” Java 8 已经允许接口中的方法体,那么为什么不允许受保护的抽象方法呢?
  • 我经常阅读这个解释,但恕我直言,这是错误的。无论谈论 OOP、通信 API 还是硬件,接口都是一种“标准交换协议”。我 PC 上的 USB 端口显然是一个公共接口。但是我主板上的插针,位于钥匙锁外壳后面,提供对可选 USB 端口的访问,显然是一个“受保护”接口。然后我们有 BIOS 芯片——它也是一个标准化的接口,但它从未以任何方式公开,只有少数公司私下知道确切的细节。所以,当然,接口可以有任何可见性!为什么不在 OOP 中?
【解决方案2】:

虽然经常引用的原因是“接口定义了公共 API”,但我认为这过于简单化了。 (而且它也“闻起来”循环逻辑。)

拥有混合访问修饰符的接口并不是没有意义的;例如部分公开,部分限制在与接口相同的包中的其他类。事实上,在某些情况下,这可能非常有用,IMO。

实际上,我认为将接口的成员隐式公开的推理部分是它使Java语言更简单

  • 隐式公共接口成员对程序员来说更容易处理。您见过多少次看似随机选择方法访问修饰符的代码(类)?许多“普通”程序员难以理解如何最好地管理 Java 抽象边界1。向接口添加 public/protected/package-private 会使它们变得更加困难。

  • 隐式公共接口成员简化了语言规范……因此 Java 编译器编写者和实现反射 API 的人的任务。

“接口定义公共 API”的思路可以说是简化语言设计决策的结果(或特征)......而不是相反。但实际上,这两种思路在 Java 设计者的头脑中可能是并行发展的。

无论如何,JDK-8179193 中对 RFE 的官方回复清楚地表明,Java 设计团队决定2允许在接口上使用 protected 会增加复杂性,但几乎没有真正的好处。感谢@skomisa 为finding the evidence

RFE 中的证据解决了这个问题。这就是官方未添加的原因。


1 - 当然,顶级程序员对这些事情没有任何困难,并且可能会欢迎更丰富的访问控制功能调色板。但是,当他们的代码交给其他人维护时会发生什么?

2 - 你可能不同意他们的决定或他们陈述的理由,但这没有实际意义。

【讨论】:

    【解决方案3】:

    我不得不说这个问题已经被 Java 8 中默认方法的引入重新打开了。我现在正在做的项目是,类似于接口的基本性质,旨在从实施。

    在某些情况下,我可以使用“默认受保护”方法大大简化我的代码。事实证明这实际上不起作用,因为接口仍然坚持 Java 7 逻辑。由于上述原因,正常的受保护方法没有任何意义;但是,如果默认的公共方法需要一个不太可能更改并且可以由受保护的方法提供的低级资源,那么在我看来,拥有“默认受保护”的工作不仅可以保持更简洁的代码,还可以保护未来的用户免受意外滥用。

    (可悲的是,这并没有改变这样一个事实,即我仍然需要使用其他不必要的抽象来过度复杂化我的代码;但我确实打算向 Oracle 提出功能请求。)

    【讨论】:

    • 我同意 100%。在引入默认方法之前,抽象类是一个明智的选择。但是,它们对多重继承的限制意味着它们并不完美。在 >= JDK 1.8 世界中,具有默认方法的接口通常是更好的选择,但是因为它们不能存储状态,所以它们依赖于定义其他抽象方法来公开状态,这意味着状态是公开的,这并不总是你想要的。
    【解决方案4】:

    因为接口定义了公共 API。任何受保护的东西都是不属于接口的内部细节。

    您可以将抽象类与受保护的抽象方法一起使用,但接口仅限于公共方法和公共静态最终字段。

    【讨论】:

    • 您说“因为接口定义了公共 API”。那么接口应该只定义public API 的原因是什么? “内部细节”和“实现细节”是有区别的,Java 中的protected 绝对不是内部细节,因为它现在是一个公共接口published to everyone 可以对其进行子类化,基本上就是整个世界。
    • Java 8 的默认方法不再适用。
    • @MarioRossi Java 9 的私有接口方法也不再适用了。
    【解决方案5】:

    这里有几个答案使用循环推理来解释为什么不能保护接口方法:因为它们必须是公共的,所以显然它们不能被保护!

    这说明不了什么,但幸运的是几年前someone raised an enhancement request for protected methods in interfaces as a JDK bug,这为这个问题提供了一些线索:

    接口中受保护的方法:跨包共享

    由于修饰符在 Java 中有点受限,因此一种共享方法的方式 跨包仅限于公共方法。有时它是 公开一个方法是危险的,但它必须是因为 缺少适当的修饰符。我的解决方案克服了这个限制。

    Java 语言规范目前不允许受保护的 接口方法的修饰符。我们可以利用这个事实 为这个新功能使用受保护的接口方法。

    如果一个接口方法被标记为受保护并且该接口是 由另一个包中的类实现,该方法不需要 是公开的,但也可以是私有的或至少是包保护的。 该方法是可见的,无论类声明它是什么,并且 在接口的源包中也可见(和子 包?)。

    这样我们可以在知名包之间共享某些方法。

    这是对该增强请求的响应,该请求已关闭,状态为Won't fix

    该提案试图以一种增加 复杂性和特殊情况几乎没有实际收益。一种典型的方式 解决这个问题是有一个实现公共的私有类 界面。实现方法是公开的,但在一个 私有类,因此它们保持私有。

    从 Java 9 开始,另一种可用的方法是创建类和 方法公开,但在具有限定导出到的模块中 特定的“朋友”模块,而不是被导出到一般 公开。

    因此,该错误报告的权威结论是:

    • 目前的情况不会改变;接口不太可能支持protected 方法。
    • 不支持接口中的protected 方法的理由是它“增加了复杂性和特殊情况而几乎没有实际收益”。
    • 从 Java 9 开始,有一种替代方法可以提供对方法的包级别访问。使用the Java Platform Module System (JPMS) 来“将类和方法公开,但在一个模块中,该模块具有对特定“朋友”模块的合格导出,而不是导出给公众”。

    【讨论】:

      【解决方案6】:

      强烈觉得接口应该允许受保护的方法;谁说界面必须对全世界的每个人都可见?至于你的观点,它可能会让“普通”(阅读:无能)程序员感到困惑:OOP 的大部分内容都是关于正确地构造对象、类、包等,如果程序员很难正确地做所有这些,他有一个更大的问题。 Java 是为这种类型的东西构建的。

      【讨论】:

      • 这不能回答问题。
      【解决方案7】:

      也许,因为它是一个接口,也就是说,它可以告诉客户他们可以对实例做什么,而不是告诉他们不能做什么。

      【讨论】:

      • 我不明白为什么一个接口不能告诉子类在不将实现暴露给外界的情况下他们可以做什么。这是在抽象类可以用作完美替代品的时候做出的设计决定。但是随着接口中默认方法的出现,抽象类现在是一个不完美的选择。
      【解决方案8】:

      由于实现类必须实现接口中声明的所有方法,如果您的实现类位于不同的包中会怎样?

      【讨论】:

        【解决方案9】:

        声明内部子接口是一种很好的做法,但从技术上讲,您不能在 Java 的接口中将内部方法声明为 protected

        当然,您可以创建另一个扩展公共接口的内部使用接口:

        package yourpackage;
        
        public interface PublicInterface {
        
            public void doThing1();
        
            public void doThing2();
        
            public void doThing3();
        
        }
        

        package yourpackage;
        
        interface InternalInterface extends PublicInterface {
        
            void doAnyInternalThing1();
        
            void doAnyInternalThing2();
        
        }
        

        您可以在包内使用InternalInterface 接口,但您应该接受PublicInterface 的任何子类型(在公共方法中):

        package yourpackage;
        
        public class SomeClass {
        
            public void someMethod(PublicInterface param) {
                if (param instanceof InternalInterface) {
                    // run the optimized code
                } else {
                    // run the general code
                }
            }
        
        }
        

        包外用户可以毫无问题地使用PublicInterface

        通常程序员在类似情况下创建抽象类。但是,在这种情况下,我们失去了多重继承的好处。

        【讨论】:

        • 包外用户也可以使用YourPublicInterface.Internal。无论public 关键字是否存在,接口中的所有内容(包括嵌套接口)都是公开的。
        【解决方案10】:

        接口如果你想使用像你描述的那样的东西,请继续使用抽象类或嵌套接口。

        Code Style 中关于接口变量的摘录,但仍然适用于方法:

        接口变量是隐式公共的,因为接口旨在提供一个应用程序编程接口 (API),Java 程序员可以完全访问该接口,以便在他们自己的应用程序中引用和实现。由于接口可能在不同于它们自己的 Java 包中使用,因此公共可见性确保程序代码可以访问该变量。

        【讨论】:

          【解决方案11】:

          唯一有意义的情况是当您想要限制对同一个包的可见性时。 protected 的所有其他用途均不适用。具体来说,protected 方法通常用于为后代提供对较低级别实现的一些细节的访问。但是在接口中声明这一点没有意义,因为没有更低级别的实现要公开。

          甚至包场景也不是真正的接口。

          要实现您可能想要的,您需要两个接口,一个供内部使用,一个在公共 API 中公开。 (可能是内部的,但不一定扩展公共的。)或者,正如其他人指出的那样,是一个抽象超类。

          【讨论】:

          • 抽象超类可以防止自己被包外的类型派生。接收到这种超类类型的引用的人,信任包的作者,可以确信该对象将按照声明的方式运行。如果一个包有多个想要公开一些通用功能的类,但不适合适当的层次结构(例如,一个实现函数 X 和 Y,一个 Y 和 Z,一个 X 和 Z),如果它可以公开将很有帮助使用接口的功能,同时仍然保证接口类型引用的实例是“真正的”。
          【解决方案12】:

          public成员一般由public成员使用。比如AbstractList.removeRange(int, int)AbstractList.clear()使用,覆盖它会提高clear的性能。

          如果接口中允许使用protected 方法,则意味着大多数public default 实现将依赖这些方法。如果子类不需要default 实现并覆盖所有public 方法,那么所有非public 方法将不再有用。如果它们是abstract,我们仍然需要覆盖它们,这使得子类变得复杂。

          【讨论】:

            【解决方案13】:

            只有当子类扩展了基类时,子类才能始终访问受保护的方法。

            在接口的情况下,子类永远不会扩展接口。它实现了接口。

            受保护的方法可以通过 extend 而不是 implement 访问。

            【讨论】:

            • 有什么区别什么关键字,没关系。
            【解决方案14】:

            接口旨在向外部世界公开方法。因此,这些方法本质上是公开的。但是,如果您想在同一系列类中引入抽象,可以通过在接口和实现类之间创建另一个抽象级别,即抽象类。下面是一个例子。

            public interface MyInterface {
                public void publicMethod(); // needs to be public
            }
            
            public abstract class MyAbstractClass implements MyInterface {
                @Override
                public void publicMethod() {
                    protectedMethod(); // you can call protected method here
                    // do other stuff
                }
                protected abstract void protectedMethod(); // can be protected
            }
            
            public class MyClass extends MyAbstractClass {
                @Override
                protected void protectedMethod() {
                    // implement protected method here, without exposing it as public
                }
            }
            

            【讨论】:

            • 但是接口中允许没有人看到的私有方法。
            • java在接口中有默认方法,即接口是绕过抽象类多重继承的拐杖。那么让抽象类的多重继承是允许的。默认方法的冲突没有成为问题。
            • 你是对的。从 Java 9 开始,接口中允许使用私有方法。这些不能是抽象的,它们在接口中实现和使用,主要是通过其他默认或静态方法(如果它们本身是静态的)。
            • 这个答案只是忽略了这个问题:为什么接口不能有受保护的方法?受保护的方法仍会“将方法暴露给外部世界”,而声称“这些方法本质上是公共的” 是错误的。它们是公开的,因为语言是这样设计的,但它可能允许接口中的受保护方法。 OP 只是在问为什么。
            【解决方案15】:

            我知道这已经很晚了,我相信你现在比我有更多的经验,所以如果这对任何人有帮助,干杯,...

            我找到了一种方法,可以让事情保持舒适的凝聚力,在处理受保护的包内时防止使用对象。

            事后看来,这似乎很容易,但事实是,如果你花任何时间尝试适应它,你想用一段代码实现的目标会变得困难 10 倍符合标准和/或针对代码可重用性进行调整。

            现实情况是,顺序会随着你的前进而展开..

            但我制作了一张图表,因为它对我有所帮助,根据问题的不同,我很容易心烦意乱。

            public interface CommonPublics {
                void publicInAll();
            }
            public static abstract class CommonProtecteds implements CommonPublics {
                protected abstract void protectedInAll();
            }
            public interface MyObjectPublics {
                void myObjectPublic();
            }
            public static class MyObject extends CommonProtecteds implements MyObjectPublics {
            
                @Override
                public void publicInAll() {
            
                }
            
                @Override
                protected void protectedInAll() {
            
                }
            
                @Override
                public void myObjectPublic() {
            
                }
            }
            

            现在当你需要处理同一个包中的protecteds时,你可以用CommonProtecteds代替处理MyObject。

            现在,理论上您可以将处理受保护的任何逻辑带入抽象中,这取决于您,但如果出于某种原因您正在处理并发,最好让所有内容尽可能紧凑并尽可能靠近同步主体.因此,在不同的锁定位置中可能需要处理的任何受保护的东西最好放在将处理它的对象中。

            【讨论】:

              猜你喜欢
              • 2013-04-07
              • 2013-09-02
              • 2016-07-25
              • 1970-01-01
              • 1970-01-01
              • 2015-12-29
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多