老问题。我很惊讶没有人引用以下标准资料:Java: James Gosling的概述,《设计模式:由四人组成的可重用的面向对象软件的元素》或Joshua Bloch的《有效Java》(在其他资料中)。
我将从报价开始:
接口只是对象响应的一组方法的规范。它不包括任何实例变量或实现。接口可以被多重继承(与类不同),并且可以比通常的刚性类继承结构更灵活地使用它们。(高斯林,第8页)
现在,让我们一个个地回答您的假设和问题(我将自动忽略Java 8功能)。
假设条件
接口是仅抽象方法和最终字段的集合。
您abstract
在Java接口中看到关键字了吗?否。那么您不应该将接口视为抽象方法的集合。也许您被C ++所谓的接口误导了,它们是仅具有纯虚方法的类。根据设计,C ++不具有(也不需要具有)接口,因为它具有多重继承。
正如Gosling解释的那样,您应该将接口视为“对象响应的一组方法”。我喜欢看界面和相关文档视为服务合同。它描述了从实现该接口的对象可以期望得到的结果。文档应指定前置条件和后置条件(例如,参数不应该为null,输出始终为正,...)和不变式(不修改对象内部状态的方法)。我认为,这份合同是OOP的心。
Java中没有多重继承。
确实。
JAVA忽略了许多C ++很少使用,理解不清,令人困惑的功能,以我们的经验,这些功能带来的痛苦多于好处。这主要包括运算符重载(尽管它确实有方法重载),多重继承和广泛的自动强制。(高斯林,第2页)
没有补充。
接口可用于在Java中实现多重继承。
不,简单,因为Java中没有多重继承。往上看。
继承的一个强项是我们可以在派生类中使用基类的代码,而无需再次编写。可能这是继承在那里最重要的事情。
这就是所谓的“实现继承”。如您所写,这是重用代码的便捷方法。
但是它有一个重要的对应物:
父类通常定义至少部分子类的物理表示形式。因为继承将子类公开给其父级实现的详细信息,所以人们常说“继承破坏了封装” [Sny86]。子类的实现与其父类的实现紧密相关,以至于父类实现的任何更改都将迫使子类进行更改。(GOF,1.6)
(在Bloch,第16项中有类似的引用。)
实际上,继承还有另一个目的:
类继承结合了接口继承和实现继承。接口继承根据一个或多个现有接口定义了一个新接口。实现继承根据一个或多个现有实现定义了一个新实现。(GOF,附录A)
两者都使用extends
Java中的关键字。您可能具有类的层次结构和接口的层次结构。第一个承担执行,第二个承担义务。
问题
Q1。由于接口仅具有抽象方法(无代码),因此如何说如果实现任何接口,那么它就是继承?我们未使用其代码。**
接口的实现不是继承。它的实现。因此就是关键词implements
。
Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?**
Java中没有多重继承。往上看。
Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。/然后为什么要创建接口?/使用接口的确切好处是什么?使用接口实现的真的是多重继承吗?
最重要的问题是:您为什么要进行多重继承?我可以想到两个答案:1.给对象赋予多重类型;2.重用代码。
给对象赋予多重类型
在OOP中,一个对象可能具有不同的类型。例如,在Java中,一个ArrayList<E>
有以下几种类型:Serializable
,Cloneable
,Iterable<E>
,Collection<E>
,List<E>
,RandomAccess
,AbstractList<E>
,AbstractCollection<E>
和Object
(我希望我没有忘记任何人)。如果对象具有不同的类型,则各种消费者将可以使用它而无需了解其特殊性。我需要一个,Iterable<E>
而你给我一个ArrayList<E>
?没关系。但是,如果我现在需要a,List<E>
而您给我a ArrayList<E>
,也可以。等等。
如何在OOP中键入对象?您以Runnable
界面为例,此示例非常适合说明该问题的答案。我引用了官方Java文档:
另外,Runnable提供了一种在不继承Thread的情况下使类活动的方法。
重点是:继承是键入对象的便捷方法。您想创建一个线程吗?让我们对该Thread
类进行子类化。您希望对象具有不同的类型,让我们使用多重继承。啊 它在Java中不存在。(在C ++中,如果您希望对象具有不同的类型,则可以采用多重继承。)
那么如何给对象赋予多重类型呢?在Java中,您可以直接输入对象。这就是您implements
对Runnable
接口进行类化时要做的事情。Runnable
如果您是继承者,为什么要使用?也许是因为您的班级已经是另一个班级的子类A
。现在您的课程有两种类型:A
和Runnable
。
通过多个接口,您可以为对象提供多种类型。您只需要创建一个具有implements
多个接口的类。只要您遵守合同,就可以。
重用代码
这是一个困难的课题;我已经引用了GOF来打破封装。其他答案提到钻石问题。您还可以想到“单一责任原则”:
一个班级只有一个改变的理由。(Robert C. Martin,敏捷软件开发,原理,模式和实践)
拥有家长班可能会给班级一个改变的理由,除了它自己的责任:
超类的实现可能因发行版本而异,如果确实如此,则即使未触摸其代码,子类也可能会中断。结果,子类必须与其父类一起演化(Bloch,项目16)。
我会添加一个比较平淡的问题:当我尝试在类中查找方法的源代码并且找不到时,总是有一种奇怪的感觉。然后我记得:必须在父类的某个位置定义它。或在祖父母阶级中。甚至更高。在这种情况下,一个好的IDE是有价值的资产,但是在我看来,它仍然是一种神奇的东西。接口层次结构没有什么相似之处,因为Javadoc是我唯一需要的东西:IDE中有一个键盘快捷键,我知道了。
继承howewer具有以下优点:
在包中使用继承是安全的,包中的子类和超类实现在同一程序员的控制下。当扩展专门为扩展而设计和记录的类时,使用继承也是安全的(项目17:为继承而设计或记录,否则禁止它)。(批注,项目16)
Java中的一个类“为扩展专门设计和记录”的示例是AbstractList
。
但是布洛赫(Bloch)和政府(GOF)坚持这一观点:“从喜好看,而不是继承”:
委派是一种使组合与继承一样强大而可重用的方法[Lie86,JZ91]。在委托中,处理请求涉及两个对象:接收对象将操作委托给委托。这类似于将请求推迟到父类的子类。(GOF第32页)
如果使用组合,则无需一次又一次地编写相同的代码。您只需创建一个处理重复的类,然后将该类的实例传递给实现该接口的类。这是重用代码的非常简单的方法。这可以帮助您遵循“单一责任原则”并使代码更具可测试性。Rust和Go没有继承(它们也没有类),但是我不认为代码比其他OOP语言更冗余。
此外,如果使用组合,您自然会发现使用接口可以使代码具有所需的结构和灵活性(请参见有关接口用例的其他答案)。
注意:您可以与Java 8接口共享代码
最后,最后一个报价:
在令人难忘的问答环节中,有人问他[James Gosling]:“如果您可以再次使用Java,您将作何改变?” “我会放弃上课”(在网上的任何地方,都不知道这是不是真的)