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

我感到困惑的是,什么时候应该使用那种interface default method而不是abstract class(带有abstract method(s))。

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

评论

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

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

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

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

@DaBlick您能否通过HashMap解决接口中的状态问题?例如:如果您想要一个Foo类,它容纳int a,b,String c。并希望它们具有状态,请创建一个HashMap >映射。当您要“实例化”理论类Foo时,可以使用方法instantiate(String nameOfFoo)进行map.put(nameOfFoo,fields),其中fields是HashMap fields.put(“ a”,new int(“ 5”)); fields.put(“ b”,new int(“ 6”)); fields.put(“ c”,“ blah”)));

#1 楼

除了默认方法实现(例如私有状态)之外,抽象类还有很多,但是从Java 8开始,无论选择哪种方法,都应该在接口中使用Defender(aka。default)方法。 />
对默认方法的约束是,只能在对其他接口方法的调用方面实现它,而不能引用特定实现的状态。因此,主要用例是更高级别的便捷方法。

这个新功能的好处是,在您被迫将抽象类用于便捷方法之前,这对实现者造成了限制。单一继承,现在您可以拥有一个真正干净的设计,仅需接口,而程序员只需执行最少的工作即可。

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

评论


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

–豹
2015年2月16日在18:47



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

– Anuroop
17年9月17日在9:41

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

– Marko Topolnik
17年9月17日在9:47

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

– Marko Topolnik
18-2-27在20:13



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

– hfontanez
18-09-9在22:13

#2 楼

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


抽象类可以具有构造函数。
抽象类具有更高的结构性并可以保留状态。

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

评论


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

–试用
2015年9月13日在20:09

@UnKnown此页面可提供更多信息:docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

–伯尼
16年6月14日在14:22

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

–LegendLength
17年7月15日在7:15

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

– Anuroop
17年9月17日在9:43

@Anuroop我不会将接口的公共static final字段描述为“状态”。静态部分意味着完全与特定实例无关。它们是在类实例化时分配的,这与实例创建后的实例不同。

– Geronimo
17/12/22在11:20

#3 楼

本文对此进行了描述。想一想Collections的forEach

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



java.util.Listjava.util.Collection接口还没有声明forEach。一种明显的解决方案是
只需将新方法添加到现有接口并在JDK中需要的地方提供
实现。但是,一旦发布,就不可能在不破坏现有实现的情况下向接口添加方法。

默认方法带来的好处是现在可以
了。 />向接口添加新的默认方法,它不会破坏
实现。


评论


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

– Andrey Chaschev
13年11月15日在10:27

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

– Marko Topolnik
13年11月15日在10:31

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

– Andrey Chaschev
13年11月15日在10:39

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

– Marko Topolnik
13年15月15日上午10:43

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

–乔治·泽维尔
19年7月25日在20:58

#4 楼

如本文所述,

Java 8中的抽象类与接口


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

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


评论


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

–Hemanth Peela
19年2月10日在17:22

如果抽象类无法实例化,为什么会有一个构造函数?

–乔治·泽维尔
19年7月24日在15:01

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

– Sujay Mohan
19-09-24在5:30



#5 楼

这两个是完全不同的:

默认方法是在不更改其状态的情况下向现有类添加外部功能。

抽象类是继承的正常类型,它们是正常的打算扩展的类。

#6 楼

关于您对

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

java文档提供了完美的答案。
抽象类与接口相比:

抽象类与接口相似。您无法实例化它们,并且它们可能包含使用或不使用实现声明的方法的混合。
但是,对于抽象类,您可以声明非静态和最终字段,并定义公共,受保护的和私有的具体方法。
通过接口,所有字段都自动为public,static和final,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,无论是否抽象,您都只能扩展一个类,而您可以实现任何数量的接口。

它们的用例已在下面的SE文章中进行了解释:
接口和抽象类之间有什么区别?

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

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

评论


现在接口也有私有方法howtodoinjava.com/java9/java9-p​​rivate-interface-methods

–valijon
19-10-9在2:29

#7 楼

每当我们在抽象类和接口之间进行选择时,我们都应始终(几乎)倾向于使用默认方法(也称为防御者或虚拟扩展)。


默认方法已结束了经典模式接口和一个伴随类,该类实现该接口中的大多数或所有方法。一个示例是Collection and AbstractCollection。现在,我们应该在接口本身中实现方法以提供默认功能。实现该接口的类可以选择重写方法或继承默认实现。


默认方法的另一个重要用途是interface evolution。假设我有一个Ball类:
public class Ball implements Collection { ... }


现在Java 8中引入了一个新功能。我们可以使用添加到接口的stream方法获得流。如果stream不是默认方法,则Collection接口的所有实现都将被破坏,因为它们将不会实现此新方法。向接口添加非默认方法不是source-compatible。但是,假设我们不重新编译该类,而是使用包含此类Ball的旧jar文件。如果没有这种缺少的方法,该类将正常加载,可以创建实例,并且看起来一切正常。但是如果程序在stream实例上调用Ball方法,我们将得到AbstractMethodError。因此,将方法设置为默认即可解决这两个问题。
Java 9在接口中甚至有私有方法,可用于封装提供默认实现的接口方法中使用的通用代码逻辑。

#8 楼

尽管它是一个老问题,但我也可以提供自己的意见。




抽象类:在抽象类内部,我们可以声明instance
变量,这是必需的到子类

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


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

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


抽象类:在Abstract类中,我们可以声明构造函数

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


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

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


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

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


抽象类:在抽象类内部,我们可以覆盖OBJECT CLASS方法

接口:我们不能在接口内部覆盖OBJECT CLASS方法。


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

#9 楼

Java接口中的默认方法可以实现接口演化。鉴于现有接口,如果希望在不破坏与旧版本接口二进制兼容性的情况下向其中添加方法,那么您有两个选择:添加默认或静态方法。实际上,添加到该接口的任何抽象方法都必须由实现此接口的类或接口来实现。

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

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


实现默认方法,并覆盖已实现接口中的实现。
重新声明该方法(不带实现),使其抽象化。
不执行任何操作(然后,已实现接口的默认方法将被简单地继承)。

有关此主题的更多信息。

#10 楼

Remi Forax的规则是您不使用Abstract类进行设计。您可以使用界面设计应用程序。无论语言是什么,Watever都是Java的版本。它受SOLID原则中的接口隔离原则的支持。

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

#11 楼


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


向后兼容:
想象一下您的接口已实现通过数百个类,修改该接口将迫使所有用户实施新添加的方法,即使对于实现您的接口的许多其他类而言可能不是必需的,而且它允许您的接口成为功能性接口

事实与限制:

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

2-必须提供一个主体

3-它不像接口中使用的其他普通方法一样抽象。

#12 楼

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

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

2)具有默认方法的接口与抽象类之间的另一个语义差异是,您可以在抽象类内定义构造函数,但是您无法在Java接口的内部定义构造函数

评论


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

–乔治·泽维尔
19年7月24日在15:00

#13 楼

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

抽象类中的方法应该提供有意义的实现,任何子类都仅在需要重写a时才应重写常见功能。

#14 楼

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

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

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

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


MyObject仅继承一个实现,但它继承了三个协定。

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

为了支持我的观点,这是《 Java》一书中的Ken Arnold和James Gosling的引用。编程语言,第4版:


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


#15 楼

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

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