何时使用:Java 8+接口默认方法与抽象方法


540

Java 8允许在称为Default Methods的接口中实现方法的默认实现。

我在什么时候使用那种interface default method而不是abstract class(with abstract method(s))之间感到困惑。

那么什么时候应该使用默认方法接口,什么时候应该使用抽象类(带有抽象方法)?在这种情况下,抽象类仍然有用吗?


38
也许您仍然不能在接口中包含字段,私有方法等,而在抽象类中却可以?
sp00m 2013年

2
我以前很想知道这个话题,现在我很清楚。感谢@Narendra Pathai。我想添加一个您针对同一主题询问的另一个主题的链接,因为这两个都是我的疑问。stackoverflow.com/questions/19998309/...
Ashutosh说兰詹

您可以在此处找到一篇不错的文章:blog.codefx.org/java/everything-about-default-methods
Fabri Pautasso

即使基类具有状态,您有时仍可以将基类编码为接口。只是接口必须为状态定义setter和getter,而具体类必须实现它们并定义字段。对此的一个限制是,在抽象类中,bean属性可以是私有的或受保护的。在接口中只有公共方法。因此,使用抽象基类的一个原因是,如果您的类具有需要私有或受保护的属性。
DaBlick

@DaBlick您能否通过HashMap解决接口中的状态问题?例如:如果您想要一个包含int a,b,String c的Foo类。并且希望它们具有状态,请创建一个HashMap </ * Foo对象的名称* /字符串,/ *字段映射* / Hashmap </ *特定于名称的字段* /字符串,/ *字段值* / Object >>映射。当您要“实例化”理论类Foo时,您有方法Instantiate(String nameOfFoo)可以执行map.put(nameOfFoo,fields),其中fields是HashMap <String,Object> fields.put(“ a”,new int(“ 5”)); fields.put(“ b”,new int(“ 6”)); fields.put(“ c”,“ blah”));
乔治Xavier

Answers:


307

除了默认方法实现(例如私有状态)之外,抽象类还有很多,但是从Java 8开始,无论选择哪种方法,都应该default在接口中使用Defender(aka。)方法。

对默认方法的限制是,只能在对其他接口方法的调用方面实现默认方法,而无需参考特定实现的状态。因此,主要用例是更高级别的便捷方法。

这项新功能的好处是,在您不得不为便利方法使用抽象类,从而将实现者限制为单一继承之前,现在您可以拥有仅接口和最少实现量的真正干净的设计程序员的努力。

default向Java 8中引入方法的最初动机是希望在不破坏任何现有实现的情况下,使用面向lambda的方法来扩展Collections Framework接口。尽管这与公共图书馆的作者更为相关,但是您可能会发现相同的功能在您的项目中也很有用。您在一个集中的地方可以添加新的便利,而不必依赖其余类型层次结构的外观。


34
通过这种推理,他们接下来要添加的是默认方法声明。我对此仍然不确定,该功能对我来说似乎更像是一种骇客,正在向所有人公开滥用。
panther,2015年

3
我可以看到Java 8时代Abstract Classes的唯一用途是用于定义非最终字段。默认情况下,在“界面”中,这些字段为最终字段,因此一旦分配它们便无法更改它们。
Anuroop

7
@Anuroop不仅是默认设置-这是唯一的选择。接口不能声明实例状态,这就是为什么抽象类要保留的原因。
Marko Topolnik

2
@PhilipRego抽象方法不调用任何东西,因为它们没有实现。类中已实现的方法可以访问类的状态(实例变量)。接口无法声明它们,因此默认方法无法访问它们。他们必须依赖提供访问状态的已实现方法的类。
Marko Topolnik

2
Marko Topolnik,您的答案仍然存在。但我想建议您更新答案。您可能还想补充一下,默认方法的优点在于,如果接口添加了新的默认方法,则该接口的先前实现不会中断。在Java 8之前并非如此
。– hfontanez

125

有一些技术差异。与Java 8接口相比,抽象类仍然可以做更多的事情:

  1. 抽象类可以具有构造函数。
  2. 抽象类更加结构化,可以保持一个状态。

从概念上讲,防御者方法的主要目的是在Java 8中引入新功能(如lambda函数)后向后兼容。


20
这个答案实际上是正确的,并且特别有意义:“从概念上讲,防御者方法的主要目的是向后兼容”
尝试

1
@UnKnown此页面可提供更多洞察力:docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
伯尼

@UnKnown,基本上,它允许您向接口添加方法,并且实现该接口的类会自动获得该功能。
LegendLength

3
关于点号的更微妙之处。以上关于“可以保持状态的2”。抽象类可以保存状态,以后可以更改。接口也可以保持状态,但是一旦在实例创建后分配了状态,就不能更改状态。
Anuroop

3
@Anuroop我不会将public static final接口的字段描述为“状态”。该static部分表示完全与特定实例无关。它们是在类实例化时分配的,这与实例创建后的实例不同
geronimo

65

这是这所描述的文章。想想forEach收藏。

List<?> list = 
list.forEach(…);

在foreach不声明java.util.List也不 java.util.Collection界面中。一个显而易见的解决方案是将新方法添加到现有接口中,并在JDK中要求的地方提供实现。但是,一旦发布,就不可能在不破坏现有实现的情况下向接口添加方法。

默认方法带来的好处是,现在可以在接口中添加新的默认方法,并且不会破坏实现。


1
“在不破坏现有实现的情况下不可能向接口添加方法”-是吗?
Andrey Chaschev 2013年

26
@AndreyChaschev如果将新方法添加到接口,则所有实现者都必须实现该新方法。因此,它破坏了现有的实现。
Marko Topolnik

4
@MarkoTopolnik谢谢,错过了。只需提及,有一种方法可以部分避免这种情况-通过在默认的抽象实现中提供此方法。对于此示例,将AbstractList::forEach抛出一个UnsupportedOperationException
Andrey Chaschev 2013年

3
@AndreyChaschev是的,这是旧方法(khm ... 是当前方法:),其缺点是将实现者限制为从提供的抽象实现中继承单个继承。
Marko Topolnik

如果发生这种情况,我不会中断,因为所有实现都提前包含了该方法。这是不可能的,但可能的。
乔治Xavier


18

正如在描述这个文章,

Java 8中的抽象类与接口

引入默认方法后,接口和抽象类似乎相同。但是,它们仍然是Java 8中的不同概念。

抽象类可以定义构造函数。它们更加结构化,并且可以具有与之关联的状态。相比之下,默认方法只能在调用其他接口方法方面实现,而无需参考特定实现的状态。因此,两者用于不同目的以及在两者之间进行选择实际上取决于场景上下文。


我相信Abstract类具有可以在Interface中定义的Constructor。因此,在Java 8中它们也彼此不同。
Hemanth Peela '19

1
如果抽象类无法实例化,为什么会有一个构造函数?
乔治·泽维尔

我们可以从子类中调用super()来调用抽象类的构造函数,这会影响抽象类的状态。
Sujay Mohan

14

关于您的查询

那么什么时候应该使用默认方法接口,什么时候应该使用抽象类呢?在这种情况下,抽象类仍然有用吗?

Java 文档提供了完美的答案。

抽象类与接口的比较:

抽象类类似于接口。您无法实例化它们,它们可能包含使用或不使用实现声明的方法的混合。

但是,使用抽象类,您可以声明非静态和最终字段,并定义公共,受保护的和私有的具体方法。

使用接口时,所有字段都自动是公共的,静态的和最终的,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,无论是否抽象,您都只能扩展一个类,而您可以实现任何数量的接口。

在以下SE帖子中已解释了它们的用例:

接口和抽象类之间有什么区别?

在这种情况下,抽象类仍然有用吗?

是。它们仍然有用。它们可以包含非静态,非最终方法 和属性(受保护的,除public以外的私有),即使使用Java-8接口也无法实现。



13

每当我们在抽象类和接口之间做出选择时,我们都应该(几乎)总是喜欢默认(也称为防御者或虚拟扩展)方法。

  1. 默认方法结束了经典的接口模式,并实现了该接口中大多数或所有方法的伴随类。一个例子是Collection and AbstractCollection。现在,我们应该在接口本身中实现方法以提供默认功能。实现接口的类可以选择重写方法或继承默认实现。
  2. 默认方法的另一个重要用途是interface evolution。假设我有一个Ball类:

    public class Ball implements Collection { ... }

现在,在Java 8中引入了新功能。我们可以通过使用stream添加到接口的方法来获取流。如果stream不是默认方法,则Collection接口的所有实现都将被破坏,因为它们将不会实现此新方法。不是向接口添加非默认方法source-compatible

但是,假设我们不重新编译该类,而是使用包含该类的旧jar文件Ball。如果没有这种缺少的方法,该类将正常加载,可以创建实例,并且看起来一切正常。但是如果程序streamBall我们的实例上调用方法将得到AbstractMethodError。因此,将方法设为默认值可以解决这两个问题。


9

Java接口中的默认方法启用接口演化

给定一个现有接口,如果希望在不破坏与该接口旧版本的二进制兼容性的情况下向其添加方法,则手边有两个选择:添加默认方法或静态方法。实际上,添加到该接口的任何抽象方法都必须由实现此接口的类或接口来实现。

静态方法是类唯一的。默认方法对于该类的实例是唯一的。

如果将默认方法添加到现有接口,则实现此接口的类和接口无需实现它。他们能

  • 实现默认方法,它将覆盖已实现接口中的实现。
  • 重新声明使它抽象的方法(无实现)。
  • 不执行任何操作(然后,已实现接口的默认方法只是继承而来)。

更多关于该主题在这里


7

尽管这是一个老问题,但我也要提供我的意见。

  1. 抽象类:在抽象类内部,我们可以声明实例变量,这是子类必需的

    接口:接口内部的每个变量始终是公共静态的,而最终我们无法声明实例变量

  2. 抽象类:抽象类可以谈论对象的状态

    接口:接口永远不能谈论对象的状态

  3. 抽象类:抽象类内部,我们可以声明构造函数

    接口:在接口内部,我们不能声明构造函数,因为构造函数的目的
    是初始化实例变量。所以如果接口中没有实例变量,那么那里的构造函数有什么需求

  4. 抽象类:在抽象类内部,我们可以声明实例和静态块

    接口:接口不能具有实例和静态块。

  5. 抽象类:抽象类不能引用lambda表达式

    接口:具有单个抽象方法的接口可以引用lambda表达式

  6. 抽象类:在抽象类内部,我们可以重写OBJECT CLASS方法

    接口:我们无法覆盖接口内部的OBJECT CLASS方法。

最后,我将指出:

接口中的默认方法概念/静态方法概念只是为了保存实现类,而没有提供有意义的有用实现。默认方法/静态方法是一种虚拟实现,“如果需要,可以在实现类中使用它们,或者可以在实现类中覆盖它们(在默认方法的情况下)”,这样就可以避免我们在接口中使用新方法时在实现类中实现新方法被添加。因此,接口永远不能等于抽象类。


5

Remi Forax的规则是您不使用Abstract类进行设计。您可以使用interfaces设计应用程序。无论语言是什么,Watever都是Java的版本。它是由支持覆盖整个院落隔离原则SOL d的原则。

您以后可以使用Abstract类来分解代码。现在,使用Java 8,您可以直接在界面中进行操作。这是一个设施,仅此而已。


2

什么时候应该使用默认方法接口,什么时候应该使用抽象类?

向后兼容: 假设您的接口由数百个类实现,修改该接口将迫使所有用户实现新添加的方法,即使对于实现该接口的许多其他类而言可能不是必需的,再加上它允许您的接口成为功能界面

事实与限制:

1-只能在接口中声明,而不能在类或抽象类中声明。

2-必须提供身体

3-假定它不像接口中使用的其他常规方法那样抽象。


1

在Java 8中,接口看起来像一个抽象类,尽管它们可能有一些区别,例如:

1)抽象类是类,因此它们不限于Java接口的其他限制,例如,抽象类可以具有state,但是在Java接口中不能具有状态。

2)具有默认方法的接口和抽象类之间的另一个语义区别是,您可以在抽象类内部定义构造函数,但是不能在Java中的接口内部定义构造函数


我同意#2的观点,但是对于#1来说,您不能仅实现接口并通过实现类获得状态吗?
乔治Xavier

0

Java Interface中的默认方法将更多地用于提供函数的虚拟实现,从而使该接口的任何实现类都免于声明所有抽象方法的麻烦,即使它们只想处理一个抽象方法。因此,接口中的默认方法在某种程度上可以替代适配器类的概念。

但是,抽象类中的方法应该提供有意义的实现,任何子类仅在需要重写通用功能时才应重写。


0

如其他答案中所述,添加了向接口添加实现的功能,以便在Collections框架中提供向后兼容性。我认为提供向后兼容性可能是将实现添加到接口的唯一好理由。

否则,如果将实现添加到接口,则将违反为什么首先添加接口的基本定律。Java是一种继承语言,与C ++允许多重继承不同。接口提供了支持多重继承的语言附带的键入好处,而不会引入多重继承带来的问题。

更具体地说,Java仅允许实现的单个继承,但允许接口的多个继承。例如,以下是有效的Java代码:

class MyObject extends String implements Runnable, Comparable { ... }

MyObject 仅继承一个实现,但它继承三个合同。

Java传递了实现的多重继承,因为实现的多重继承带来了许多棘手的问题,这些问题不在此答案的范围之内。添加了接口以允许合同的多个继承(aka接口)而没有实现的多个继承的问题。

为了支持我的观点,这里引用了Java编程语言,第四版,Ken Arnold和James Gosling的话:

单一继承排除了一些有用且正确的设计。多重继承的问题源于实现的多重继承,但是在许多情况下,多重继承被用来继承许多抽象协定以及一个具体的实现。提供一种在不继承实现的情况下继承抽象协定的方法,可以带来多重继承的键入优势,而不会带来多重实现继承的问题。抽象协定的继承称为 接口继承。Java编程语言通过允许您声明interface类型来支持接口继承。


-1

请首先考虑开放/封闭原则。接口中的默认方法将其删除。这是Java中的一个坏功能。它鼓励不良的设计,不良的体系结构和低质量的软件。我建议避免完全使用默认方法。

问自己几个问题:为什么不能将方法放入抽象类?那么您需要多个抽象类吗?然后考虑一下您的班级负责什么。您确定要放在单个类中的所有方法确实都能达到相同的目的吗?可能您会区分几个目的,然后将您的班级分为几个班级,每个目的都有自己的班级。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.