這篇文章主要介紹了Java8默認(rèn)方法Default Methods原理是什么,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了岑鞏免費(fèi)建站歡迎大家使用!Java 8 引入了新的語(yǔ)言特性——默認(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)有庫(kù)的接口中,并能確保與采用舊版本接口編寫的代碼的二進(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 語(yǔ)言的重要特性而出現(xiàn)之際,為升級(jí)舊接口且保持向后兼容(backward compatibility)提供了途徑。
String[] array = new String[] { "hello", ", ", "world", }; Listlist = 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{ 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(); // 打印:“InterfaceD 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ī)制,為的是避免多繼承帶來(lái)的調(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í),無(wú)法通過(guò) 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í)都來(lái)自于 InterfaceA 接口,InterfaceB 接口中的“同名方法”只是繼承自 InterfaceA 接口而來(lái)并對(duì)其進(jìn)行了覆寫。ClassA 類實(shí)現(xiàn)的兩個(gè)接口不是兩個(gè)毫不相干的接口,因此不存在同名歧義方法。
而覆寫意味著對(duì)父類方法的屏蔽,這也是 Override 的設(shè)計(jì)意圖之一。因此在實(shí)現(xiàn)了 InterfaceB 接口的類中無(wú)法訪問已被覆寫的 InterfaceA 接口中的 foo 方法。
這是當(dāng)接口繼承行為發(fā)生沖突時(shí)的規(guī)則之一,即 被其它類型所覆蓋的方法會(huì)被忽略。
如果想要調(diào)用 InterfaceA 接口中的 foo 方法,只能通過(guò)自定義一個(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ò)。
通過(guò)這個(gè)例子,應(yīng)該注意到在使用一個(gè)默認(rèn)方法前,一定要考慮它是否真的需要。因?yàn)?默認(rèn)方法會(huì)帶給程序歧義,并且在復(fù)雜的繼承體系中容易產(chǎn)生編譯錯(cuò)誤。濫用默認(rèn)方法可能給代碼帶來(lái)意想不到、莫名其妙的錯(cuò)誤。
接口與抽象類
當(dāng)接口繼承行為發(fā)生沖突時(shí)的另一個(gè)規(guī)則是,類的方法聲明優(yōu)先于接口默認(rèn)方法,無(wú)論該方法是具體的還是抽象的。
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(); // 打?。骸癐nterfaceA 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ū)嵗椒?,無(wú)法保存 狀態(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 語(yǔ)句的 default 分支),不能用在抽象類中。
接口默認(rèn)方法不能覆寫 Object 類的 equals、hashCode 和 toString 方法。
接口中的靜態(tài)方法必須是 public 的,public 修飾符可以省略,static 修飾符不能省略。
即使使用了 java 8 的環(huán)境,一些 IDE 仍然可能在一些代碼的實(shí)時(shí)編譯提示時(shí)出現(xiàn)異常的提示(例如無(wú)法發(fā)現(xiàn) java 8 的語(yǔ)法錯(cuò)誤),因此不要過(guò)度依賴 IDE。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Java8默認(rèn)方法Default Methods原理是什么”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián)建站,關(guān)注創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站www.cdcxhl.com,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。