這篇文章主要講解了“Java8默認(rèn)方法DefaultMethods的原理及實(shí)例用法”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Java8默認(rèn)方法DefaultMethods的原理及實(shí)例用法”吧!
為興文等地區(qū)用戶(hù)提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及興文網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、興文網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶(hù)提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶(hù)的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!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ù)的接口中,并能確保與采用舊版本接口編寫(xiě)的代碼的二進(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 類(lèi)并沒(méi)有實(shí)現(xiàn) InterfaceA 接口中的 foo 方法,InterfaceA 接口中提供了 foo 方法的默認(rèn)實(shí)現(xiàn),因此可以直接調(diào)用 ClassA 類(lèi)的 foo 方法。
為什么要有默認(rèn)方法
在 java 8 之前,接口與其實(shí)現(xiàn)類(lèi)之間的 耦合度 太高了(tightly coupled),當(dāng)需要為一個(gè)接口添加方法時(shí),所有的實(shí)現(xiàn)類(lèi)都必須隨之修改。默認(rèn)方法解決了這個(gè)問(wèn)題,它可以為接口添加新的方法,而不會(huì)破壞已有的接口的實(shí)現(xiàn)。這在 lambda 表達(dá)式作為 java 8 語(yǔ)言的重要特性而出現(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)類(lèi)。
下面的代碼展示了 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(); // 打印:“InterfaceC 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 接口):
不覆寫(xiě)默認(rèn)方法,直接從父接口中獲取方法的默認(rèn)實(shí)現(xiàn)。
覆寫(xiě)默認(rèn)方法,這跟類(lèi)與類(lèi)之間的覆寫(xiě)規(guī)則相類(lèi)似。
覆寫(xiě)默認(rèn)方法并將它重新聲明為抽象方法,這樣新接口的子類(lèi)必須再次覆寫(xiě)并實(shí)現(xiàn)這個(gè)抽象方法。
默認(rèn)方法的多繼承
Java 使用的是單繼承、多實(shí)現(xiàn)的機(jī)制,為的是避免多繼承帶來(lái)的調(diào)用歧義的問(wèn)題。當(dāng)接口的子類(lèi)同時(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 類(lèi)中,它實(shí)現(xiàn)的 InterfaceA 接口和 InterfaceB 接口中的方法不存在歧義,可以直接多實(shí)現(xiàn)。
在 ClassB 類(lèi)中,它實(shí)現(xiàn)的 InterfaceB 接口和 InterfaceC 接口中都存在相同簽名的 foo 方法,需要手動(dòng)解決沖突。覆寫(xiě)存在歧義的方法,并可以使用 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 類(lèi)多實(shí)現(xiàn) InterfaceA 接口和 InterfaceB 接口時(shí),不會(huì)出現(xiàn)方法名歧義的錯(cuò)誤。當(dāng) ClassB 類(lèi)覆寫(xiě) foo 方法時(shí),無(wú)法通過(guò) InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法。
因?yàn)?InterfaceB 接口繼承了 InterfaceA 接口,那么 InterfaceB 接口一定包含了所有 InterfaceA 接口中的字段方法,因此一個(gè)同時(shí)實(shí)現(xiàn)了 InterfaceA 接口和 InterfaceB 接口的類(lèi)與一個(gè)只實(shí)現(xiàn)了 InterfaceB 接口的類(lèi)完全等價(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(); // 打印:“ClassD foo” }}
這里的 classC.foo(); 同樣調(diào)用的是 ClassD 類(lèi)中的 foo 方法,打印結(jié)果為“ClassD foo”,因?yàn)?ClassC 類(lèi)中的 foo 方法在 ClassD 類(lèi)中被覆寫(xiě)了。
在上面的 ClassA 類(lèi)中不會(huì)出現(xiàn)方法名歧義的原因是所謂“存在歧義”的方法其實(shí)都來(lái)自于 InterfaceA 接口,InterfaceB 接口中的“同名方法”只是繼承自 InterfaceA 接口而來(lái)并對(duì)其進(jìn)行了覆寫(xiě)。ClassA 類(lèi)實(shí)現(xiàn)的兩個(gè)接口不是兩個(gè)毫不相干的接口,因此不存在同名歧義方法。
而覆寫(xiě)意味著對(duì)父類(lèi)方法的屏蔽,這也是 Override 的設(shè)計(jì)意圖之一。因此在實(shí)現(xiàn)了 InterfaceB 接口的類(lèi)中無(wú)法訪(fǎng)問(wèn)已被覆寫(xiě)的 InterfaceA 接口中的 foo 方法。
這是當(dāng)接口繼承行為發(fā)生沖突時(shí)的規(guī)則之一,即 被其它類(lèi)型所覆蓋的方法會(huì)被忽略。
如果想要調(diào)用 InterfaceA 接口中的 foo 方法,只能通過(guò)自定義一個(gè)新的接口同樣繼承 InterfaceA 接口并顯示地覆寫(xiě) foo 方法,在方法中使用 InterfaceA.super.foo(); 調(diào)用 InterfaceA 接口的 foo 方法,最后讓實(shí)現(xiàn)類(lèi)同時(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è)覆寫(xiě) 不能省略,否則 InterfaceC 接口中繼承自 InterfaceA 接口的隱式的 foo 方法同樣會(huì)被認(rèn)為是被 InterfaceB 接口覆寫(xiě)了而被屏蔽,會(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ò)誤。
接口與抽象類(lèi)
當(dāng)接口繼承行為發(fā)生沖突時(shí)的另一個(gè)規(guī)則是,類(lèi)的方法聲明優(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 類(lèi)中并不需要手動(dòng)覆寫(xiě) bar 方法,因?yàn)閮?yōu)先考慮到 ClassA 類(lèi)繼承了的 AbstractClassA 抽象類(lèi)中存在對(duì) bar 方法的實(shí)現(xiàn),同樣的因?yàn)?AbstractClassA 抽象類(lèi)中的 foo 方法是抽象的,所以在 ClassA 類(lèi)中必須實(shí)現(xiàn) foo 方法。
雖然 Java 8 的接口的默認(rèn)方法就像抽象類(lèi),能提供方法的實(shí)現(xiàn),但是他們倆仍然是 不可相互代替的:
接口可以被類(lèi)多實(shí)現(xiàn)(被其他接口多繼承),抽象類(lèi)只能被單繼承。 接口中沒(méi)有 this 指針,沒(méi)有構(gòu)造函數(shù),不能擁有實(shí)例字段(實(shí)例變量)或?qū)嵗椒?,無(wú)法保存 狀態(tài)(state),抽象方法中可以。 抽象類(lèi)不能在 java 8 的 lambda 表達(dá)式中使用。 從設(shè)計(jì)理念上,接口反映的是 “l(fā)ike-a” 關(guān)系,抽象類(lèi)反映的是 “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(); // 打印:“hello, world” }}
其他注意點(diǎn)
default 關(guān)鍵字只能在接口中使用(以及用在 switch 語(yǔ)句的 default 分支),不能用在抽象類(lèi)中。 接口默認(rèn)方法不能覆寫(xiě) Object 類(lèi)的 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ò)度依賴(lài) IDE。
感謝各位的閱讀,以上就是“Java8默認(rèn)方法DefaultMethods的原理及實(shí)例用法”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Java8默認(rèn)方法DefaultMethods的原理及實(shí)例用法這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司,,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
新聞標(biāo)題:Java8默認(rèn)方法DefaultMethods的原理及實(shí)例用法-創(chuàng)新互聯(lián)
當(dāng)前路徑:http://www.rwnh.cn/article10/pgddo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷(xiāo)、小程序開(kāi)發(fā)、定制開(kāi)發(fā)、定制網(wǎng)站、企業(yè)建站、動(dòng)態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容