Java 8允许在称为Default Methods的接口中实现方法的默认实现。
我在什么时候使用那种interface default method
而不是abstract class
(with abstract method(s)
)之间感到困惑。
那么什么时候应该使用默认方法接口,什么时候应该使用抽象类(带有抽象方法)?在这种情况下,抽象类仍然有用吗?
Java 8允许在称为Default Methods的接口中实现方法的默认实现。
我在什么时候使用那种interface default method
而不是abstract class
(with abstract method(s)
)之间感到困惑。
那么什么时候应该使用默认方法接口,什么时候应该使用抽象类(带有抽象方法)?在这种情况下,抽象类仍然有用吗?
Answers:
除了默认方法实现(例如私有状态)之外,抽象类还有很多,但是从Java 8开始,无论选择哪种方法,都应该default
在接口中使用Defender(aka。)方法。
对默认方法的限制是,只能在对其他接口方法的调用方面实现默认方法,而无需参考特定实现的状态。因此,主要用例是更高级别的便捷方法。
这项新功能的好处是,在您不得不为便利方法使用抽象类,从而将实现者限制为单一继承之前,现在您可以拥有仅接口和最少实现量的真正干净的设计程序员的努力。
default
向Java 8中引入方法的最初动机是希望在不破坏任何现有实现的情况下,使用面向lambda的方法来扩展Collections Framework接口。尽管这与公共图书馆的作者更为相关,但是您可能会发现相同的功能在您的项目中也很有用。您在一个集中的地方可以添加新的便利,而不必依赖其余类型层次结构的外观。
有一些技术差异。与Java 8接口相比,抽象类仍然可以做更多的事情:
从概念上讲,防御者方法的主要目的是在Java 8中引入新功能(如lambda函数)后向后兼容。
public static final
接口的字段描述为“状态”。该static
部分表示完全与特定实例无关。它们是在类实例化时分配的,这与实例创建后的实例不同。
这是这所描述的文章。想想forEach
收藏。
List<?> list = …
list.forEach(…);
在foreach不声明
java.util.List
也不java.util.Collection
界面中。一个显而易见的解决方案是将新方法添加到现有接口中,并在JDK中要求的地方提供实现。但是,一旦发布,就不可能在不破坏现有实现的情况下向接口添加方法。默认方法带来的好处是,现在可以在接口中添加新的默认方法,并且不会破坏实现。
AbstractList::forEach
抛出一个UnsupportedOperationException
。
这两个完全不同:
默认方法是在不更改其状态的情况下向现有类添加外部功能。
抽象类是继承的普通类型,它们是打算扩展的普通类。
正如在描述这个文章,
Java 8中的抽象类与接口
引入默认方法后,接口和抽象类似乎相同。但是,它们仍然是Java 8中的不同概念。
抽象类可以定义构造函数。它们更加结构化,并且可以具有与之关联的状态。相比之下,默认方法只能在调用其他接口方法方面实现,而无需参考特定实现的状态。因此,两者用于不同目的以及在两者之间进行选择实际上取决于场景上下文。
关于您的查询
那么什么时候应该使用默认方法接口,什么时候应该使用抽象类呢?在这种情况下,抽象类仍然有用吗?
Java 文档提供了完美的答案。
抽象类与接口的比较:
抽象类类似于接口。您无法实例化它们,它们可能包含使用或不使用实现声明的方法的混合。
但是,使用抽象类,您可以声明非静态和最终字段,并定义公共,受保护的和私有的具体方法。
使用接口时,所有字段都自动是公共的,静态的和最终的,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,无论是否抽象,您都只能扩展一个类,而您可以实现任何数量的接口。
在以下SE帖子中已解释了它们的用例:
在这种情况下,抽象类仍然有用吗?
是。它们仍然有用。它们可以包含非静态,非最终方法 和属性(受保护的,除public以外的私有),即使使用Java-8接口也无法实现。
每当我们在抽象类和接口之间做出选择时,我们都应该(几乎)总是喜欢默认(也称为防御者或虚拟扩展)方法。
Collection and AbstractCollection
。现在,我们应该在接口本身中实现方法以提供默认功能。实现接口的类可以选择重写方法或继承默认实现。默认方法的另一个重要用途是interface evolution
。假设我有一个Ball类:
public class Ball implements Collection { ... }
现在,在Java 8中引入了新功能。我们可以通过使用stream
添加到接口的方法来获取流。如果stream
不是默认方法,则Collection
接口的所有实现都将被破坏,因为它们将不会实现此新方法。不是向接口添加非默认方法source-compatible
。
但是,假设我们不重新编译该类,而是使用包含该类的旧jar文件Ball
。如果没有这种缺少的方法,该类将正常加载,可以创建实例,并且看起来一切正常。但是如果程序stream
在Ball
我们的实例上调用方法将得到AbstractMethodError
。因此,将方法设为默认值可以解决这两个问题。
尽管这是一个老问题,但我也要提供我的意见。
抽象类:在抽象类内部,我们可以声明实例变量,这是子类必需的
接口:接口内部的每个变量始终是公共静态的,而最终我们无法声明实例变量
抽象类:抽象类可以谈论对象的状态
接口:接口永远不能谈论对象的状态
抽象类:在抽象类内部,我们可以声明构造函数
接口:在接口内部,我们不能声明构造函数,因为构造函数的目的
是初始化实例变量。所以如果接口中没有实例变量,那么那里的构造函数有什么需求。
抽象类:在抽象类内部,我们可以声明实例和静态块
接口:接口不能具有实例和静态块。
抽象类:抽象类不能引用lambda表达式
接口:具有单个抽象方法的接口可以引用lambda表达式
抽象类:在抽象类内部,我们可以重写OBJECT CLASS方法
接口:我们无法覆盖接口内部的OBJECT CLASS方法。
最后,我将指出:
接口中的默认方法概念/静态方法概念只是为了保存实现类,而没有提供有意义的有用实现。默认方法/静态方法是一种虚拟实现,“如果需要,可以在实现类中使用它们,或者可以在实现类中覆盖它们(在默认方法的情况下)”,这样就可以避免我们在接口中使用新方法时在实现类中实现新方法被添加。因此,接口永远不能等于抽象类。
Remi Forax的规则是您不使用Abstract类进行设计。您可以使用interfaces设计应用程序。无论语言是什么,Watever都是Java的版本。它是由支持我覆盖整个院落隔离原则在SOL 我 d的原则。
您以后可以使用Abstract类来分解代码。现在,使用Java 8,您可以直接在界面中进行操作。这是一个设施,仅此而已。
什么时候应该使用默认方法接口,什么时候应该使用抽象类?
向后兼容: 假设您的接口由数百个类实现,修改该接口将迫使所有用户实现新添加的方法,即使对于实现该接口的许多其他类而言可能不是必需的,再加上它允许您的接口成为功能界面
事实与限制:
1-只能在接口中声明,而不能在类或抽象类中声明。
2-必须提供身体
3-假定它不像接口中使用的其他常规方法那样抽象。
在Java 8中,接口看起来像一个抽象类,尽管它们可能有一些区别,例如:
1)抽象类是类,因此它们不限于Java接口的其他限制,例如,抽象类可以具有state,但是在Java接口中不能具有状态。
2)具有默认方法的接口和抽象类之间的另一个语义区别是,您可以在抽象类内部定义构造函数,但是不能在Java中的接口内部定义构造函数
如其他答案中所述,添加了向接口添加实现的功能,以便在Collections框架中提供向后兼容性。我认为提供向后兼容性可能是将实现添加到接口的唯一好理由。
否则,如果将实现添加到接口,则将违反为什么首先添加接口的基本定律。Java是一种继承语言,与C ++允许多重继承不同。接口提供了支持多重继承的语言附带的键入好处,而不会引入多重继承带来的问题。
更具体地说,Java仅允许实现的单个继承,但允许接口的多个继承。例如,以下是有效的Java代码:
class MyObject extends String implements Runnable, Comparable { ... }
MyObject
仅继承一个实现,但它继承三个合同。
Java传递了实现的多重继承,因为实现的多重继承带来了许多棘手的问题,这些问题不在此答案的范围之内。添加了接口以允许合同的多个继承(aka接口)而没有实现的多个继承的问题。
为了支持我的观点,这里引用了Java编程语言,第四版,Ken Arnold和James Gosling的话:
单一继承排除了一些有用且正确的设计。多重继承的问题源于实现的多重继承,但是在许多情况下,多重继承被用来继承许多抽象协定以及一个具体的实现。提供一种在不继承实现的情况下继承抽象协定的方法,可以带来多重继承的键入优势,而不会带来多重实现继承的问题。抽象协定的继承称为 接口继承。Java编程语言通过允许您声明
interface
类型来支持接口继承。