609

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

我对何时使用那种interface default method, 而不是abstract class(with abstract method(s)) 感到困惑。

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

4

16 回答 16

357

抽象类比默认方法实现(例如私有状态)要多得多,但是从 Java 8 开始,只要您可以选择其中任何一种,就应该default在接口中使用 defer(又名)方法。

默认方法的限制是它只能在调用其他接口方法的情况下实现,而不参考特定实现的状态。所以主要的用例是更高层次和方便的方法。

这个新特性的好处是,在您被迫使用抽象类作为便利方法之前,因此将实现者限制为单一继承,现在您可以拥有一个非常干净的设计,只需要接口和最少的实现强加给程序员的努力。

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

于 2013-11-15T10:24:58.220 回答
144

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

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

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

于 2013-11-15T10:21:48.847 回答
71

本文对此进行了描述。想想forEach收藏。

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

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

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

于 2013-11-15T10:15:55.747 回答
31

本文所述,

Java 8 中的抽象类与接口

引入 Default Method 之后,似乎接口和抽象类是一样的。但是,它们在 Java 8 中仍然是不同的概念。

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

于 2015-02-03T16:45:37.730 回答
21

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

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

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

    public class Ball implements Collection { ... }

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

但是假设我们不重新编译该类并使用包含该类的旧 jar 文件Ball。如果没有这个缺失的方法,该类将正常加载,可以创建实例,并且似乎一切正常。但是stream如果程序在实例上调用方法,Ball我们将得到AbstractMethodError. 所以让方法默认解决了这两个问题。

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

于 2015-09-25T04:35:16.820 回答
19

这两个是完全不同的:

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

抽象类是一种普通的继承类型,它们是旨在扩展的普通类。

于 2013-11-15T10:25:52.317 回答
17

尽管这是一个老问题,但我也可以就此发表意见。

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

    接口:在接口内部,每个变量总是 public static 和 final 我们不能声明实例变量

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

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

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

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

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

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

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

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

  6. 抽象类:在抽象类中,我们可以覆盖 OBJECT CLASS 方法

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

我将在结束时指出:

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

于 2019-11-14T06:35:38.767 回答
15

关于您的查询

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

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

抽象类与接口的比较:

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

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

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

在下面的 SE 帖子中解释了它们中的每一个的用例:

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

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

是的。它们仍然有用。它们可以包含非静态、非最终方法 和属性(protected、private 和 public),即使使用 Java-8 接口也是不可能的。

于 2016-06-22T15:01:24.090 回答
9

Java 接口中的默认方法使接口演化成为可能。

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

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

如果在现有接口中添加默认方法,则实现该接口的类和接口不需要实现它。他们能

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

更多关于这里的话题。

于 2015-06-23T11:36:40.727 回答
6

Remi Forax规则是您不使用抽象类进行设计。你用接口设计你的应用程序。Waterever 是 Java 的版本,不管是什么语言。它以SOL I D原则的接口隔离原则为后盾。

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

于 2017-03-23T08:36:26.557 回答
2

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

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

事实与限制:

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

2-必须提供一个身体

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

于 2016-08-05T17:01:49.990 回答
1

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

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

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

于 2019-02-12T10:24:44.077 回答
0

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

否则,如果您将实现添加到接口,您将违反最初添加接口的基本规律。Java 是一种单继承语言,与允许多重继承的 C++ 不同。接口提供了支持多重继承的语言所带来的打字好处,而不会引入多重继承带来的问题。

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

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

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

Java传递了实现的多重继承,因为实现的多重继承带来了许多棘手的问题,这些问题超出了这个答案的范围。添加了接口以允许合约的多重继承(又名接口),而不会出现实现的多重继承问题。

为了支持我的观点,这里引用了来自The Java Programming Language, 4th edition一书的 Ken Arnold 和 James Gosling 的话:

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

于 2019-08-30T05:24:19.923 回答
0

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

假设某个企业主想与 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");

    }
}
于 2021-06-24T22:35:01.450 回答
0

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

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

于 2018-02-25T20:00:19.323 回答
-2

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

问自己几个问题:为什么不能将方法放到抽象类中?那么你需要一个以上的抽象类吗?然后想想你的班级负责什么。您确定要放在单个类中的所有方法都真正实现相同的目的吗?可能你会区分几个目的,然后将你的班级分成几个班级,每个目的都有自己的班级。

于 2017-12-27T08:55:00.573 回答