亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java8默認(rèn)方法Default Methods原理及實(shí)例詳解

 更新時(shí)間:2020年01月03日 09:06:30   作者:Ebn Zhang  
這篇文章主要介紹了Java8默認(rèn)方法Default Methods原理及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

這篇文章主要介紹了Java8默認(rèn)方法Default Methods原理及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

Java 8 引入了新的語言特性——默認(rèn)方法(Default Methods)。

Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.

默認(rèn)方法允許您添加新的功能到現(xiàn)有庫的接口中,并能確保與采用舊版本接口編寫的代碼的二進(jìn)制兼容性。

默認(rèn)方法是在接口中的方法簽名前加上了 default 關(guān)鍵字的實(shí)現(xiàn)方法。

一個(gè)簡(jiǎn)單的例子

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }
}

class ClassA implements InterfaceA {
}

public class Test {
  public static void main(String[] args) {
    new ClassA().foo(); // 打?。骸癐nterfaceA foo”
  }
}

ClassA 類并沒有實(shí)現(xiàn) InterfaceA 接口中的 foo 方法,InterfaceA 接口中提供了 foo 方法的默認(rèn)實(shí)現(xiàn),因此可以直接調(diào)用 ClassA 類的 foo 方法。

為什么要有默認(rèn)方法

在 java 8 之前,接口與其實(shí)現(xiàn)類之間的 耦合度 太高了(tightly coupled),當(dāng)需要為一個(gè)接口添加方法時(shí),所有的實(shí)現(xiàn)類都必須隨之修改。默認(rèn)方法解決了這個(gè)問題,它可以為接口添加新的方法,而不會(huì)破壞已有的接口的實(shí)現(xiàn)。這在 lambda 表達(dá)式作為 java 8 語言的重要特性而出現(xiàn)之際,為升級(jí)舊接口且保持向后兼容(backward compatibility)提供了途徑。

String[] array = new String[] {
    "hello",
    ", ",
    "world",
};
List<String> list = Arrays.asList(array);
list.forEach(System.out::println); // 這是 jdk 1.8 新增的接口默認(rèn)方法

這個(gè) forEach 方法是 jdk 1.8 新增的接口默認(rèn)方法,正是因?yàn)橛辛四J(rèn)方法的引入,才不會(huì)因?yàn)?Iterable 接口中添加了 forEach 方法就需要修改所有 Iterable 接口的實(shí)現(xiàn)類。

下面的代碼展示了 jdk 1.8 的 Iterable 接口中的 forEach 默認(rèn)方法:

package java.lang;

import java.util.Objects;
import java.util.function.Consumer;

public interface Iterable<T> {
  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
      action.accept(t);
    }
  }
}

默認(rèn)方法的繼承

和其它方法一樣,接口默認(rèn)方法也可以被繼承。

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }
}

interface InterfaceB extends InterfaceA {
}

interface InterfaceC extends InterfaceA {
  @Override
  default void foo() {
    System.out.println("InterfaceC foo");
  }
}

interface InterfaceD extends InterfaceA {
  @Override
  void foo();
}

public class Test {
  public static void main(String[] args) {
    new InterfaceB() {}.foo(); // 打?。骸癐nterfaceA foo”
    new InterfaceC() {}.foo(); // 打?。骸癐nterfaceC foo”
    new InterfaceD() {
      @Override
      public void foo() {
        System.out.println("InterfaceD foo");
      }
    }.foo(); // 打?。骸癐nterfaceD foo”
    
    // 或者使用 lambda 表達(dá)式
    ((InterfaceD) () -> System.out.println("InterfaceD foo")).foo();
  }
}

接口默認(rèn)方法的繼承分三種情況(分別對(duì)應(yīng)上面的 InterfaceB 接口、InterfaceC 接口和 InterfaceD 接口):

不覆寫默認(rèn)方法,直接從父接口中獲取方法的默認(rèn)實(shí)現(xiàn)。

覆寫默認(rèn)方法,這跟類與類之間的覆寫規(guī)則相類似。

覆寫默認(rèn)方法并將它重新聲明為抽象方法,這樣新接口的子類必須再次覆寫并實(shí)現(xiàn)這個(gè)抽象方法。

默認(rèn)方法的多繼承

Java 使用的是單繼承、多實(shí)現(xiàn)的機(jī)制,為的是避免多繼承帶來的調(diào)用歧義的問題。當(dāng)接口的子類同時(shí)擁有具有相同簽名的方法時(shí),就需要考慮一種解決沖突的方案。

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }
}

interface InterfaceB {
  default void bar() {
    System.out.println("InterfaceB bar");
  }
}

interface InterfaceC {
  default void foo() {
    System.out.println("InterfaceC foo");
  }
  
  default void bar() {
    System.out.println("InterfaceC bar");
  }
}

class ClassA implements InterfaceA, InterfaceB {
}

// 錯(cuò)誤
//class ClassB implements InterfaceB, InterfaceC {
//}

class ClassB implements InterfaceB, InterfaceC {
  @Override
  public void bar() {
    InterfaceB.super.bar(); // 調(diào)用 InterfaceB 的 bar 方法
    InterfaceC.super.bar(); // 調(diào)用 InterfaceC 的 bar 方法
    System.out.println("ClassB bar"); // 做其他的事
  }
}

在 ClassA 類中,它實(shí)現(xiàn)的 InterfaceA 接口和 InterfaceB 接口中的方法不存在歧義,可以直接多實(shí)現(xiàn)。

在 ClassB 類中,它實(shí)現(xiàn)的 InterfaceB 接口和 InterfaceC 接口中都存在相同簽名的 foo 方法,需要手動(dòng)解決沖突。覆寫存在歧義的方法,并可以使用 InterfaceName.super.methodName(); 的方式手動(dòng)調(diào)用需要的接口默認(rèn)方法。

接口繼承行為發(fā)生沖突時(shí)的解決規(guī)則

值得注意的是這么一種情況:

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }
}

interface InterfaceB extends InterfaceA {
  @Override
  default void foo() {
    System.out.println("InterfaceB foo");
  }
}

// 正確
class ClassA implements InterfaceA, InterfaceB {
}

class ClassB implements InterfaceA, InterfaceB {
  @Override
  public void foo() {
//    InterfaceA.super.foo(); // 錯(cuò)誤
    InterfaceB.super.foo();
  }
}

當(dāng) ClassA 類多實(shí)現(xiàn) InterfaceA 接口和 InterfaceB 接口時(shí),不會(huì)出現(xiàn)方法名歧義的錯(cuò)誤。當(dāng) ClassB 類覆寫 foo 方法時(shí),無法通過 InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法。

因?yàn)?InterfaceB 接口繼承了 InterfaceA 接口,那么 InterfaceB 接口一定包含了所有 InterfaceA 接口中的字段方法,因此一個(gè)同時(shí)實(shí)現(xiàn)了 InterfaceA 接口和 InterfaceB 接口的類與一個(gè)只實(shí)現(xiàn)了 InterfaceB 接口的類完全等價(jià)。

這很好理解,就相當(dāng)于 class SimpleDateFormat extends DateFormat 與 class SimpleDateFormat extends DateFormat, Object 等價(jià)(如果允許多繼承)。

或者換種方式理解:

class ClassC {
  public void foo() {
    System.out.println("ClassC foo");
  }
}

class ClassD extends ClassC {
  @Override
  public void foo() {
    System.out.println("ClassD foo");
  }
}

public class Test {
  public static void main(String[] args) {
    ClassC classC = new ClassD();
    classC.foo(); // 打?。骸癈lassD foo”
  }
}

這里的 classC.foo(); 同樣調(diào)用的是 ClassD 類中的 foo 方法,打印結(jié)果為“ClassD foo”,因?yàn)?ClassC 類中的 foo 方法在 ClassD 類中被覆寫了。

在上面的 ClassA 類中不會(huì)出現(xiàn)方法名歧義的原因是所謂“存在歧義”的方法其實(shí)都來自于 InterfaceA 接口,InterfaceB 接口中的“同名方法”只是繼承自 InterfaceA 接口而來并對(duì)其進(jìn)行了覆寫。ClassA 類實(shí)現(xiàn)的兩個(gè)接口不是兩個(gè)毫不相干的接口,因此不存在同名歧義方法。

而覆寫意味著對(duì)父類方法的屏蔽,這也是 Override 的設(shè)計(jì)意圖之一。因此在實(shí)現(xiàn)了 InterfaceB 接口的類中無法訪問已被覆寫的 InterfaceA 接口中的 foo 方法。

這是當(dāng)接口繼承行為發(fā)生沖突時(shí)的規(guī)則之一,即 被其它類型所覆蓋的方法會(huì)被忽略。

如果想要調(diào)用 InterfaceA 接口中的 foo 方法,只能通過自定義一個(gè)新的接口同樣繼承 InterfaceA 接口并顯示地覆寫 foo 方法,在方法中使用 InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法,最后讓實(shí)現(xiàn)類同時(shí)實(shí)現(xiàn) InterfaceB 接口和自定義的新接口,代碼如下:

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }
}

interface InterfaceB extends InterfaceA {
  @Override
  default void foo() {
    System.out.println("InterfaceB foo");
  }
}

interface InterfaceC extends InterfaceA {
  @Override
  default void foo() {
    InterfaceA.super.foo();
  }
}

class ClassA implements InterfaceB, InterfaceC {
  @Override
  public void foo() {
    InterfaceB.super.foo();
    InterfaceC.super.foo();
  }
}

注意! 雖然 InterfaceC 接口的 foo 方法只是調(diào)用了一下父接口的默認(rèn)實(shí)現(xiàn)方法,但是這個(gè)覆寫 不能省略,否則 InterfaceC 接口中繼承自 InterfaceA 接口的隱式的 foo 方法同樣會(huì)被認(rèn)為是被 InterfaceB 接口覆寫了而被屏蔽,會(huì)導(dǎo)致調(diào)用 InterfaceC.super.foo() 時(shí)出錯(cuò)。

通過這個(gè)例子,應(yīng)該注意到在使用一個(gè)默認(rèn)方法前,一定要考慮它是否真的需要。因?yàn)?默認(rèn)方法會(huì)帶給程序歧義,并且在復(fù)雜的繼承體系中容易產(chǎn)生編譯錯(cuò)誤。濫用默認(rèn)方法可能給代碼帶來意想不到、莫名其妙的錯(cuò)誤。

接口與抽象類

當(dāng)接口繼承行為發(fā)生沖突時(shí)的另一個(gè)規(guī)則是,類的方法聲明優(yōu)先于接口默認(rèn)方法,無論該方法是具體的還是抽象的。

interface InterfaceA {
  default void foo() {
    System.out.println("InterfaceA foo");
  }

  default void bar() {
    System.out.println("InterfaceA bar");
  }
}

abstract class AbstractClassA {
  public abstract void foo();

  public void bar() {
    System.out.println("AbstractClassA bar");
  }
}

class ClassA extends AbstractClassA implements InterfaceA {
  @Override
  public void foo() {
    InterfaceA.super.foo();
  }
}

public class Test {
  public static void main(String[] args) {
    ClassA classA = new ClassA();
    classA.foo(); // 打印:“InterfaceA foo”
    classA.bar(); // 打?。骸癆bstractClassA bar”
  }
}

ClassA 類中并不需要手動(dòng)覆寫 bar 方法,因?yàn)閮?yōu)先考慮到 ClassA 類繼承了的 AbstractClassA 抽象類中存在對(duì) bar 方法的實(shí)現(xiàn),同樣的因?yàn)?AbstractClassA 抽象類中的 foo 方法是抽象的,所以在 ClassA 類中必須實(shí)現(xiàn) foo 方法。

雖然 Java 8 的接口的默認(rèn)方法就像抽象類,能提供方法的實(shí)現(xiàn),但是他們倆仍然是 不可相互代替的:

  • 接口可以被類多實(shí)現(xiàn)(被其他接口多繼承),抽象類只能被單繼承。
  • 接口中沒有 this 指針,沒有構(gòu)造函數(shù),不能擁有實(shí)例字段(實(shí)例變量)或?qū)嵗椒?,無法保存 狀態(tài)(state),抽象方法中可以。
  • 抽象類不能在 java 8 的 lambda 表達(dá)式中使用。
  • 從設(shè)計(jì)理念上,接口反映的是 “l(fā)ike-a” 關(guān)系,抽象類反映的是 “is-a” 關(guān)系。

接口靜態(tài)方法

除了默認(rèn)方法,Java 8 還在允許在接口中定義靜態(tài)方法。

interface InterfaceA {
  default void foo() {
    printHelloWorld();
  }
  
  static void printHelloWorld() {
    System.out.println("hello, world");
  }
}

public class Test {
  public static void main(String[] args) {
    InterfaceA.printHelloWorld(); // 打?。骸癶ello, world”
  }
}

其他注意點(diǎn)

  • default 關(guān)鍵字只能在接口中使用(以及用在 switch 語句的 default 分支),不能用在抽象類中。
  • 接口默認(rèn)方法不能覆寫 Object 類的 equals、hashCode 和 toString 方法。
  • 接口中的靜態(tài)方法必須是 public 的,public 修飾符可以省略,static 修飾符不能省略。
  • 即使使用了 java 8 的環(huán)境,一些 IDE 仍然可能在一些代碼的實(shí)時(shí)編譯提示時(shí)出現(xiàn)異常的提示(例如無法發(fā)現(xiàn) java 8 的語法錯(cuò)誤),因此不要過度依賴 IDE。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java程序員容易犯的10大低級(jí)錯(cuò)誤

    Java程序員容易犯的10大低級(jí)錯(cuò)誤

    這篇文章主要介紹了Java程序員容易犯的10大低級(jí)錯(cuò)誤,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-09-09
  • Spring Boot實(shí)現(xiàn)郵件發(fā)送必會(huì)的5種姿勢(shì)

    Spring Boot實(shí)現(xiàn)郵件發(fā)送必會(huì)的5種姿勢(shì)

    這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)郵件發(fā)送必會(huì)的5種姿勢(shì),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • spring cloud實(shí)現(xiàn)前端跨域問題的解決方案

    spring cloud實(shí)現(xiàn)前端跨域問題的解決方案

    這篇文章主要介紹了 spring cloud實(shí)現(xiàn)前端跨域問題的解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • 詳解eclipse中Maven工程使用Tomcat7以上插件的方法

    詳解eclipse中Maven工程使用Tomcat7以上插件的方法

    本篇文章主要介紹了詳解eclipse中Maven工程使用Tomcat7以上插件的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • Java數(shù)據(jù)類型超詳細(xì)示例講解

    Java數(shù)據(jù)類型超詳細(xì)示例講解

    Java程序中要求參與的計(jì)算的數(shù)據(jù),必須要保證數(shù)據(jù)類型的一致性,如果數(shù)據(jù)類型不一致將發(fā)生類型的轉(zhuǎn)換。本文將通過示例詳細(xì)說說Java中數(shù)據(jù)類型的轉(zhuǎn)換,感興趣的可以了解一下
    2022-11-11
  • SpringBoot+mail 輕松實(shí)現(xiàn)各類郵件自動(dòng)推送

    SpringBoot+mail 輕松實(shí)現(xiàn)各類郵件自動(dòng)推送

    在實(shí)際的項(xiàng)目開發(fā)過程中,經(jīng)常需要用到郵件通知功能,例如,通過郵箱注冊(cè),郵箱找回密碼,郵箱推送報(bào)表等等,實(shí)際的應(yīng)用場(chǎng)景非常的多,今天通過這篇文章,我們一起來學(xué)習(xí)如何在 Spring Boot 中快速實(shí)現(xiàn)一個(gè)自動(dòng)發(fā)送郵件的功能
    2024-07-07
  • Spring為singleton?bean注入prototype?bean

    Spring為singleton?bean注入prototype?bean

    這篇文章主要介紹了Spring為singleton?bean注入prototype?bean,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-07-07
  • JVM要雙親委派的原因及如何打破它

    JVM要雙親委派的原因及如何打破它

    平時(shí)做業(yè)務(wù)開發(fā)比較少接觸類加載器,但是如果想深入學(xué)習(xí),了解類加載的原理是必不可少的.java的類加載器有哪些?什么是雙親委派?為什么要雙親委派?如何打破它?接下來本文就帶大家詳細(xì)介紹這些知識(shí) ,需要的朋友可以參考下
    2021-06-06
  • Java基礎(chǔ)類庫之StringBuffer類用法詳解

    Java基礎(chǔ)類庫之StringBuffer類用法詳解

    String類是在所有開發(fā)項(xiàng)目開發(fā)之中一定會(huì)使用的一個(gè)功能類。雖然String類很好用,但也有弊端——內(nèi)容不允許頻繁修改,所以為了解決問題,我們提供了StringBuffer類。本文就來講講StringBuffer類的用法
    2022-07-07
  • Spring中BeanFactory與FactoryBean接口的區(qū)別詳解

    Spring中BeanFactory與FactoryBean接口的區(qū)別詳解

    這篇文章主要給大家介紹了關(guān)于Spring中BeanFactory與FactoryBean接口的區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03

最新評(píng)論