与Java 8中的默认方法和抽象类的接口

时间:2013-11-15 10:06:02

标签: java interface java-8 abstract-class default-method

由于Java 8允许在名为Default Methods的接口中默认实现方法,因此在何时使用abstract class之间似乎存在混淆。

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

16 个答案:

答案 0 :(得分:260)

抽象类比默认方法实现(例如私有状态)要多得多,但是从Java 8开始,无论何时你都可以选择,你应该选择防御者(又名。default)界面中的方法。

对默认方法的约束是它只能在对其他接口方法的调用方面实现,而不引用特定实现的状态。所以主要的用例是更高层次和更方便的方法。

这个新功能的好处在于,在您被迫使用抽象类来实现便捷方法之前,从而将实现者约束为单继承,现在您可以只使用接口和一个非常干净的设计最少的程序员实施工作量。

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

答案 1 :(得分:99)

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

  1. 抽象类可以有一个构造函数。
  2. 抽象类更结构化,可以保持状态。
  3. 从概念上讲,defender方法的主要目的是在Java 8中引入新功能(如lambda函数)后向后兼容。

答案 2 :(得分:58)

这在article中有所描述。考虑一下forEach收藏。

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

java.util.List并未声明forEach   java.util.Collection界面。一个明显的解决方案是   只需将新方法添加到现有界面并提供   在JDK中需要的实现。但是,一旦发表,它   是不可能在没有破坏的情况下向接口添加方法   现有的实施。

     

默认方法带来的好处是现在可以实现   向界面添加一个新的默认方法,它不会破坏   的实施方式。

答案 3 :(得分:15)

this文章中所述,

Java 8中的抽象类与接口

  

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

     

抽象类可以定义构造函数。他们更有条理   可以有一个与他们相关的州。相反,默认情况下   方法只能在调用其他方面实现   接口方法,没有参考特定的实现   州。因此,两者都用于不同目的并在两者之间进行选择   实际上取决于场景背景。

答案 4 :(得分:14)

这两者完全不同:

默认方法是向现有类添加外部功能,而不更改其状态。

抽象类是一种正常的继承类型,它们是普通类,它们都是为了扩展。

答案 5 :(得分:12)

关于

的查询
  

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

java documentation 提供了完美的答案。

抽象类与接口相比:

  

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

     

但是,对于抽象类,您可以声明非静态和最终的字段,并定义public,protected和private具体方法。

     

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

以下SE帖子中解释了每个用例:

What is the difference between an interface and abstract class?

  

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

是。它们仍然有用。它们可以包含非静态,非最终方法和属性(除了公共之外,受保护,私有),即使使用Java-8接口也是如此。

答案 6 :(得分:11)

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

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

    public class Ball implements Collection { ... }

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

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

答案 7 :(得分:6)

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

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

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

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

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

有关here主题的更多信息。

答案 8 :(得分:4)

Remi Forax规则您不使用抽象类进行设计。您使用界面设计应用程序。 Watever是Java的版本,无论语言是什么。它以SOLID原则中的 I 接口隔离原则为后盾。

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

答案 9 :(得分:4)

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

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

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

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

    接口:接口永远无法谈论对象的状态

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

    接口:在内部接口中,我们无法声明构造函数作为
    的目的   构造函数用于初始化实例变量。所以呢   如果我们不能有实例,是那里的构造函数的需要   接口中的变量

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

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

  5. 抽象类:抽象类无法引用lambda表达式

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

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

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

我将在笔记上结束:

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

答案 10 :(得分:1)

  

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

向后兼容性: 想象一下,你的界面是由数百个类实现的,修改该界面会强制所有用户实现新添加的方法,即使它对于实现你的界面的许多其他类来说也不是必不可少的,而且它允许你的界面是一个 功能界面

事实&amp;限制:

1-May只能在界面内声明,而不是在类或 抽象类。

2 - 必须提供身体

3 - 不假设它是抽象的,就像接口中使用的其他常规方法一样。

答案 11 :(得分:1)

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

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

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

答案 12 :(得分:0)

请首先考虑开放/封闭原则。接口中的默认方法DO VIOLATE它。这是Java中的一个不好的功能。它鼓励糟糕的设计,糟糕的架构,低软件质量。我建议完全避免使用默认方法。

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

答案 13 :(得分:0)

Java接口中的默认方法将更多地用于提供函数的虚拟实现,从而节省该接口的任何实现类,即使他们只想处理一个抽象方法,也不会宣布所有抽象方法。 因此,接口中的默认方法更多地取代了适配器类的概念。

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

答案 14 :(得分:0)

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

否则,如果将实现添加到接口,则将打破为什么首先添加接口的基本定律。 Java是一种继承语言,与C ++不同,它允许多次继承。接口提供了支持多重继承的语言所具有的打字优势,而不会引入多重继承所带来的问题。

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

select cast(datetime as date), 
           datediff(mi,min(case when Type='Entry' then datetime end),
           max(case when Type='Exit' then datetime end))
    from tablename
    group by cast(datetime as date)

class MyObject extends String implements Runnable, Comparable { ... } 仅继承一个实现,但它继承三个协定。

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

为支持我的观点,以下是来自 Java编程语言,第四版

的Ken Arnold和James Gosling的引用:
  

单一继承会排除一些有用且正确的设计。的   多重继承的问题来自于   实现,但在许多情况下,使用多重继承来   继承一些抽象合同,也许一个具体合同   实施。提供继承抽象合同的方法   在不继承实现的情况下,可以带来以下好处:   多重继承而没有多重实现的问题   遗产。抽象合同的继承称为   接口继承。 Java编程语言通过允许您声明MyObject类型来支持接口继承

答案 15 :(得分:0)

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

假设某个企业主想与亚马逊和沃尔玛合作,那么这里定义的接口将是 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");

    }
}