【问题标题】:Java Interface - Fatal FlawJava 接口 - 致命缺陷
【发布时间】:2024-01-21 19:18:01
【问题描述】:

假设我们有一个接口A,由B类实现:

public interface A {
    public int getValueForName(String name);
}

public class B implements A {

    public int getValueForName(String name) {
        //implementation
    }

    public String getNameForValue(int value) {
        //implementation
    }

}

如果程序员在每次引用B 的实例时始终使用A 类型,那么在B 中定义但在A 中未指定的任何方法(例如getNameForValue())都会被隐藏并且无法访问通过任何包含A 类型引用的代码。

这种方法似乎有一个致命的缺陷。一段引用B(类型为A)实例的代码如何访问getNameForValue()

【问题讨论】:

  • 为什么任何想要A 的代码都需要执行mA 本质上明确地将 m 排除在任何想要 A 的人的知识之外——这就是重点。任何想要A 的人都不应该关心除A 定义之外的任何事情......
  • 哇,哇,为什么会被搁置?对此有一个完全合理的答案,我觉得这源于对接口提供的内容的误解。 Edit:元讨论can be found here.
  • @MadProgrammer:这如何明确排除m,因为如果我们在B 类中编写一个方法,那么它应该在接口A 中定义。对吧?
  • @bddesai89 我试图找到一种表达方式。但是任何期望A 的东西都只期望A 定义的东西,它永远不应该关心BB 定义的东西超出了它与A 的联系,因此,A 明确排除了任何未定义的东西A来自那些想与A合作的知识...
  • 您评论中的代码编译并运行良好,但该代码与您的问题有什么关系?如果它是main(String args[]) { A b = new B(); b.m(); },这将是相关的。这不会编译,因为 b 只知道实现 A,而不是具体的 B 对象。

标签: java interface casting


【解决方案1】:

真正的缺陷是没有遵守接口的约定。如果我使用的是接口,我不应该关心除了接口中定义的方法之外的任何其他方法。

如果接口没有定义我想要使用的方法,那么要么需要更新接口以适应新方法,要么我需要使用不同的接口。

Collection 为例。如果我有一个绑定到Collection 合同的实例,那么我可以访问Iterator。但是,我知道我要返回的元素是 List 类型,我想改用更强大的 ListIterator

问题是 Collection 没有为我定义任何访问ListIterator的方式。所以,我只有一个选择(因为我不能随便去更新Collection 接口):改用List 接口。

如果您在自己的代码中遇到这种情况,您的接口不支持它们需要的方法。在interface级别而不是instance级别添加该支持。

【讨论】:

  • interface A { } class B implements A { void m() { System.out.println("Hello World"); } } class seven { public static void main(String args[]) { B b = new B(); b.m(); } } 编译并运行这个程序。效果绝对好
  • @bddesai89:是的,我希望它能够工作 - 您正在使用可以访问 m() 的具体类型。
【解决方案2】:

一段引用 B 实例(类型 A)的代码如何访问 getNameForValue()?

通过将实例转换为类型 B。

接口的目的之一是定义两种或多种类型共有的方法。这并不意味着您要为每种可能类型的每种可能组合定义接口方法,而只是那些您希望通过接口公开的所有类型共有的方法。

因此,根据定义,拥有接口类型的实例假定原始类型中可能存在您无法通过接口访问的方法。这就是它的工作原理。

您可能拥有接口的另一个原因是指定功能。所以如果一个类是可迭代的,那意味着它可以被迭代。如果我试图获取一个 Iterable 实例,我关心类中实现 Iterable 方法的方法,但我不关心任何其他方法,因为它们与 Iterable 能力没有任何关系。

简而言之,这是一个特点,而不是一个缺陷。

【讨论】:

    【解决方案3】:

    从一个更真实的例子来看它。如果我被蒙上眼睛,你告诉我面前有一只家庭宠物,我该怎么办?大概是宠物吧。现在,如果你告诉我这是一只仓鼠,我也可以把它放在*里让它到处跑(另一方面,我的猫不会同意这个想法)。

    当您将变量声明为接口(或更高级别的类)时,例如您的A,它与上面示例中的家庭宠物相同。因此,并非所有方法都可供您使用。

    要知道它是仓鼠还是B,您必须取下眼罩。对于 Java,这意味着调用instanceof,然后将变量转换为B。您需要instanceof 来确保演员表是安全的。当然,除非您知道该事物是 B,在这种情况下,您可能希望实际将其声明为 B

    如果您发现自己在铸造,您的设计可能做错了什么。自从 Java 有了泛型,就很少需要再强制转换了。

    【讨论】:

    • 您将仓鼠和猫组合成一个非常有意义的具体 Java 示例。 +1。