有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

为什么必须用Java声明接口?

有时,我们有几个类,它们的某些方法具有相同的签名,但与声明的Java接口不对应。例如,JTextFieldJButton(以及javax.swing.*中的其他几个)都有一个方法

public void addActionListener(ActionListener l)

现在,假设我想对有这种方法的对象做些什么;然后,我希望有一个接口(或者自己定义),例如

  public interface CanAddActionListener {
      public void addActionListener(ActionListener l);
  }

这样我就可以写:

  public void myMethod(CanAddActionListener aaa, ActionListener li) {
         aaa.addActionListener(li);
         ....

但遗憾的是,我不能:

     JButton button;
     ActionListener li;
     ...
     this.myMethod((CanAddActionListener)button,li);

这种演员阵容是非法的。编译器知道JButton不是a CanAddActionListener,因为类尚未声明实现该接口<然而它“实际上”实现了它

这有时会带来不便——Java本身已经修改了几个核心类,以实现一个由旧方法构成的新接口(String implements CharSequence,例如)

我的问题是:为什么会这样?我理解声明类实现接口的实用性。但是无论如何,看看我的例子,为什么编译器不能推断类JButton“满足”接口声明(查看内部)并接受强制转换?这是编译器效率的问题,还是还有更根本的问题

我对答案的总结:在这种情况下,Java本可以考虑一些“结构类型”(有点像鸭子类型——但在编译时检查)。没有。除了一些(我不清楚)性能和实现方面的困难,还有一个更基本的概念:在Java中,接口的声明(一般来说,所有内容的声明)不仅仅是结构上的,而是语义上的:这些方法应该实现一些特定的行为/意图。因此,一个在结构上满足某些接口的类(即,它具有具有所需签名的方法)不一定在语义上满足它(一个极端的例子:回想一下“标记接口”,它甚至没有方法!)。因此,Java可以断言一个类实现了一个接口,因为(而且仅仅因为)这个接口已经被显式声明。其他语言(Go、Scala)也有其他的理念


共 (2) 个答案

  1. # 1 楼答案

    Why can't the compiler deduce that the class JButton "satisfies" the interface declaration (looking inside it) and accept the cast? Is it an issue of compiler efficiency or there are more fundamental problems?

    这是一个更根本的问题

    接口的要点是指定有一个通用的API/行为集,许多类都支持该API/行为集。因此,当类被声明为implements SomeInterface时,类中签名与接口中的方法签名匹配的任何方法都被假定为提供该行为的方法

    相比之下,如果语言只是匹配基于签名的方法。。。不管界面如何。。。然后,当两个具有相同签名的方法实际上意味着/做了语义上不相关的事情时,我们很容易得到错误的匹配

    (后一种方法的名称是“duck typing”……Java不支持它。)


    维基百科type systems的页面上说,duck类型既不是“提名类型”,也不是“结构类型”。相比之下,皮尔斯甚至没有提到“duck类型”,但他对主格类型和结构类型的定义如下:

    "Type systems like Java's, in which names [of types] are significant and subtyping is explicitly declared, are called nominal. Type systems like most of the ones in this book in which names are inessential and subtyping is defined directly on the structure of the types, are called structural."

    因此,根据皮尔斯的定义,duck类型是一种结构类型,尽管它通常使用运行时检查来实现。(皮尔斯的定义独立于编译时和运行时检查。)

    参考:

    • “类型和编程语言”——本杰明·C·皮尔斯,麻省理工学院出版社,2002年,ISBN 0-26216209-1
  2. # 2 楼答案

    Java的设计选择是让实现类明确声明它们实现的接口,这只是一种设计选择。可以肯定的是,JVM已经针对这个选择进行了优化,实现另一个选择(比如Scala的结构类型)可能需要额外的成本,除非添加一些新的JVM指令

    那么,的设计选择到底是关于什么的呢?这一切都归结于方法的语义。考虑一下:以下方法在语义上是相同的吗

    • 绘制(字符串图形ShapeName)
    • 绘制(字符串handgunName)
    • draw(字符串播放CardName)

    这三种方法都有签名draw(String)。人类可能会通过阅读一些文档来推断它们与参数名的语义不同。机器有没有办法分辨出它们是不同的

    Java的设计选择是要求类的开发人员明确声明方法符合预定义接口的语义:

    interface GraphicalDisplay {
        ...
        void draw(String graphicalShapeName);
        ...
    }
    
    class JavascriptCanvas implements GraphicalDisplay {
        ...
        public void draw(String shape);
        ...
    }
    

    毫无疑问,JavascriptCanvas中的draw方法旨在匹配图形显示的draw方法。如果有人试图通过一个将要拔出手枪的物体,机器可以检测到错误

    Go的设计选择更加自由,允许在事后定义接口。一个具体的类不需要声明它实现了什么接口。相反,新纸牌游戏组件的设计者可能会声明,提供扑克牌的对象必须具有与签名draw(String)匹配的方法。这样做的优点是,使用该方法的任何现有类都可以在不必更改其源代码的情况下使用,但缺点是该类可能会拿出手枪而不是扑克牌

    duck类型化语言的设计选择是完全不用正式接口,只需匹配方法签名。接口(或“协议”)的任何概念都是纯惯用的,没有直接的语言支持

    这只是许多可能的设计选择中的三种。这三个问题可以概括如下:

    Java:程序员必须明确声明自己的意图,机器将对此进行检查。假设程序员可能会犯语义错误(图形/手枪/卡片)

    Go:程序员必须至少声明其意图的一部分,但机器在检查时需要进行的操作更少。假设程序员可能会犯笔误(整数/字符串),但不太可能犯语义错误(图形/手枪/卡片)

    鸭子打字:程序员无需表达任何意图,机器也无需检查。假设程序员不太可能犯笔误或语义错误

    本回答的范围不包括接口和键入是否足以测试文书和语义错误。完整的讨论将不得不考虑编译时编译技术、自动化测试方法、运行时/热点编译和其他许多问题。p>

    人们承认draw(String)这个例子是故意夸大的。真正的例子将涉及更丰富的类型,这将提供更多的线索来消除方法的歧义