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

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

我很困惑什么时候我会使用那种interface default method,而不是abstract class(与abstract method(s))。

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

156197 次浏览

这是描述在文章。想想forEach系列。

List<?> list = …
list.forEach(…);
java.util.Listjava.util.List都没有声明forEach java.util.Collection接口尚未。一个显而易见的解决办法是 只需将新方法添加到现有接口并提供 JDK中需要的实现。然而,一旦发表,它 添加方法到接口而不破坏 现有的实现。< / p > 默认方法带来的好处是现在可以 添加一个新的默认方法到接口,它不会破坏 实现。< / p >

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

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

从概念上讲,防御方法的主要目的是在Java 8中引入新特性(如lambda-functions)后实现向后兼容性。

抽象类比默认方法实现(如私有状态)要多得多,但从Java 8开始,无论何时您可以选择其中任何一种,都应该选择防御器(也就是私有状态)。default)方法。

默认方法的限制是它只能通过调用其他接口方法来实现,而不能引用特定实现的状态。所以主要的用例是高级和方便的方法。

这个新特性的好处在于,以前您必须使用抽象类来实现方便的方法,从而将实现者限制为单一继承,现在您可以拥有一个真正干净的设计,只需要接口,并且强制程序员进行最少的实现工作。

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

这两个是完全不同的:

默认方法是将添加外部功能转换为现有类而不改变它们的状态。

抽象类是一种正常的继承类型,它们是正常的课程,用于扩展。

文章所述,

Java 8中的抽象类与接口

在介绍了默认方法之后,似乎接口和 抽象类是一样的。然而,它们仍然是不同的概念 在Java 8中,

抽象类可以定义构造函数。他们更有条理 可以有一个与它们相关联的状态。相反,违约 方法只能以调用other的方式实现 接口方法,不引用特定的实现 状态。因此,两者都用于不同的目的,并在两者之间进行选择

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

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

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

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

  • 实现默认方法,它将覆盖已实现接口中的实现。
  • 重新声明方法(没有实现),使其抽象。
  • 什么都不做(然后简单地继承实现接口的默认方法)。

更多关于在这里的话题。

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

  1. 默认方法结束了经典的接口模式和一个实现该接口中大部分或所有方法的伴生类。例如Collection and AbstractCollection。现在我们应该在接口本身中实现方法,以提供默认功能。实现接口的类可以选择覆盖方法或继承默认实现。

  2. 默认方法的另一个重要用途是interface evolution。假设我有一个类球:

    # EYZ0

现在在Java 8中引入了一个新特性流。我们可以通过使用添加到接口的stream方法来获取流。如果stream不是默认方法,那么Collection接口的所有实现都将失效,因为它们不会实现这个新方法。向接口添加非默认方法不是source-compatible

但是假设我们不重新编译这个类,而是使用一个旧的jar文件,其中包含这个类Ball。如果没有这个缺失的方法,类将很好地加载,可以创建实例,似乎一切都在正常工作。如果程序在Ball实例上调用stream方法,我们将得到AbstractMethodError。因此,使方法默认解决了这两个问题。

Java 9在接口中甚至有私有方法,可以用来封装提供默认实现的接口方法中使用的公共代码逻辑。

关于你对…

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

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

抽象类与接口的比较:

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

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

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

它们各自的用例已经在下面的SE帖子中解释了:

接口和抽象类的区别是什么?< / >

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

是的。它们仍然有用。它们是可以包含非静态、非最终的方法吗和属性(受保护,除了公共之外还有私人),这即使在Java-8接口中也是不可能的。

什么时候接口应该使用默认方法,什么时候接口应该使用默认方法 抽象类被使用?< / p >

< >强向后兼容性: 想象一下,你的接口是由数百个类实现的,修改这个接口将迫使所有用户实现新添加的方法,即使它对于实现你的接口的许多其他类来说可能不是必需的。另外,它允许你的接口是< em > < / em >功能接口

# EYZ0

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

2 .必须提供一个主体

它不像接口中使用的其他常规方法那样被认为是抽象的。

雷米Forax规则是您不使用抽象类进行设计。你用界面来设计你的应用。无论Java的版本是什么,无论语言是什么。它是由索尔<强> < /强> D原则中的# eyz2接口隔离原则支持的。

稍后您可以使用抽象类来分解代码。现在有了Java 8,你可以直接在界面中完成。这只是个设施,仅此而已。

请首先考虑开/闭原则。接口中的默认方法确实违反了它。这是Java中的一个不好的特性。它助长了糟糕的设计、糟糕的架构和低软件质量。我建议完全避免使用默认方法。

问自己几个问题: 为什么不能把方法放到抽象类中?那么您是否需要多个抽象类呢?然后想想你的类负责什么。您确定要放入单个类中的所有方法都实现了相同的目的吗?可能你会区分几个目的,然后将你的类分成几个类,每个目的有自己的类。

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

然而,抽象类中的方法应该给出一个有意义的实现,只有在需要覆盖一个通用功能时,任何子类才应该覆盖它。

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

1)抽象类是类,所以它们不受Java中接口的其他限制,例如抽象类可以有这样的状态,但是在Java中你不能有接口上的状态。

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

正如在其他回答中提到的,添加实现到接口的能力是为了在Collections框架中提供向后兼容性。我认为,提供向后兼容性可能是向接口添加实现的唯一好的理由。

否则,如果您将实现添加到接口中,那么您就违反了最初添加接口的基本规律。 Java是一种单一继承语言,不像c++允许多重继承。接口提供了支持多重继承的语言所具有的类型优势,而不会引入多重继承所带来的问题。

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

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

MyObject只继承了一个实现,但它继承了三个契约。

Java传递了实现的多重继承,因为实现的多重继承带来了许多棘手的问题,这些问题超出了本文的讨论范围。添加接口是为了允许合约的多重继承(又名接口),而不存在实现的多重继承问题。

为了支持我的观点,这里引用Ken Arnold和James Gosling在Java编程语言,第4版一书中的一段话:

单一继承排除了一些有用和正确的设计。的 的多重继承引起了多重继承问题 实现,但在许多情况下多继承是习惯的 继承一些抽象契约和一个具体契约 实现。提供一种继承抽象契约的方法 的类型优点而无需继承实现 多重继承,没有多重实现的问题 继承。抽象契约的继承被称为继承 接口继承< / em >。Java编程语言通过允许您声明interface类型来支持接口继承

虽然这是一个老问题,但让我也谈谈我的看法。

    在抽象类中,我们可以声明instance 这些变量是子类

    所必需的 在接口内部,每个变量总是公共静态的 最后,我们不能声明实例变量

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

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

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

    在接口内部,我们不能声明构造函数作为
    的目的 构造函数用于初始化实例变量。那么 如果我们不能有实例,构造函数是否需要 接口变量.

  3. . txt
  4. 在抽象类中,我们可以声明实例块和静态块

    接口不能有实例块和静态块

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

    只有一个抽象方法的接口可以引用lambda表达式

  6. 抽象类:在抽象类中,我们可以重写OBJECT class方法

    我们不能覆盖接口内部的OBJECT CLASS方法

最后,我要指出:

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

从业务用例上下文中,接口可用于定义特定的业务规则,其中抽象类将定义启动业务的公共结构。

假设一些企业所有者希望与Amazon和Walmart合作,那么这里定义的接口将是WalmartPartnerAmazonPartner将定义特定的业务规则,抽象类BusinessSetup将获得特定区域的业务设置。

// Interfaces
 

public interface WalmartPartner {
public static boolean signUpForWalmartBusinessAccount(String BusinessId){
System.out.println("Setting up Walmart Business Partner");
return true;
}
public default  void  getWalmartDeals(){
System.out.println("Default walmart deal executed !");
}
public abstract void setupShopifyForWalmart();
public abstract  void setupWalmartProducts();


public interface AmazonPartner {
public static boolean signUpAsAmazonServicePartner(String BusinessId){
System.out.println("Setting up Amazon Business Partner");
return true;
}
public default  void  paymentPlatformSetup(){
System.out.println(" Amazon default payment platform is setup");
}
public abstract void setupPrimeMemberDealsByRegion();
public abstract  void setupPrimeDeals();
}


// Abstract class


public abstract class BusinessSetup {
String businessId ;
public BusinessSetup(String businessId){
this.businessId = businessId;
System.out.println("1. Initial Business setup for BusienssID: "+this.businessId+" is Complete");
}
public final boolean getBusinessRegisteredInRegion(String region){
System.out.println("2. Business got registered in "+region+ "!");
return true;
}
public abstract void setupCustomerPlatform(String customerId);
public abstract void setupVendorPlatform(String vendorId);


}


// Concrete Class
public class WalMartPartnerImpl extends BusinessSetup implements WalmartPartner {
public WalMartPartnerImpl(String businessId) {
super(businessId);
}
@Override
public void setupCustomerPlatform(String customerId) {
}


@Override
public void setupVendorPlatform(String vendorId) {
}


@Override
public void setupShopifyForWalmart() {
}


@Override
public void setupWalmartProducts() {
}
public static void main(String args[]){
WalMartPartnerImpl walMartPartner = new WalMartPartnerImpl("wal8989");
walMartPartner.getBusinessRegisteredInRegion("california");
walMartPartner.getWalmartDeals();
walMartPartner.setupCustomerPlatform("wal8989");


}
}