《Java8实战》笔记(09):默认方法
默認(rèn)方法
本文的源碼
實(shí)現(xiàn)接口的類必須為接口中定義的每個(gè)方法提供一個(gè)實(shí)現(xiàn),或者從父類中繼承它的實(shí)現(xiàn)。但是,一旦類庫的設(shè)計(jì)者需要更新接口,向其中加入新的方法,這種方式就會(huì)出現(xiàn)問題。現(xiàn)實(shí)情況是,現(xiàn)存的實(shí)體類往往不在接口設(shè)計(jì)者的控制范圍之內(nèi),這些實(shí)體類為了適配新的接口約定也需要進(jìn)行修改。由于Java 8的API在現(xiàn)存的接口上引入了非常多的新方法,這種變化帶來的問題也愈加嚴(yán)重,一個(gè)例子就是像Guava和Apache Commons這樣的框架現(xiàn)在都需要修改實(shí)現(xiàn)了List接口的所有類,為其添加sort方法的實(shí)現(xiàn)。
Java 8為了解決這一問題引入了一種新的機(jī)制。Java 8中的接口現(xiàn)在支持在聲明方法的同時(shí)提供實(shí)現(xiàn)!通過兩種方式可以完成這種操作。
換句話說,接口能提供方法的具體實(shí)現(xiàn)。因此,實(shí)現(xiàn)接口的類如果不顯式地提供該方法的具體實(shí)現(xiàn),就會(huì)自動(dòng)繼承默認(rèn)的實(shí)現(xiàn)。這種機(jī)制可以使你平滑地進(jìn)行接口的優(yōu)化和演進(jìn)。實(shí)際上,到目前為止你已經(jīng)使用了多個(gè)默認(rèn)方法。
兩個(gè)例子
List接口中的sort方法是Java 8中全新的方法,它的定義如下:
default void sort(Comparator<? super E> c){Collections.sort(this, c); }Collection中的stream方法的定義如下
default Stream<E> stream() {return StreamSupport.stream(spliterator(), false); }默認(rèn)方法的引入就是為了以兼容的方式解決像Java API這樣的類庫的演進(jìn)問題的
不斷演進(jìn)的API
為了理解為什么一旦API發(fā)布之后,它的演進(jìn)就變得非常困難,
假設(shè)你是一個(gè)流行Java繪圖庫的設(shè)計(jì)者。
你的庫中包含了一個(gè)Resizable接口,它定義了一個(gè)簡(jiǎn)單的可縮放形狀必須支持的很多方法, 比如:setHeight、setWidth、getHeight、getWidth以及setAbsoluteSize。此外,你還提供了幾個(gè)額外的實(shí)現(xiàn)(out-of-box implementation),如正方形、長(zhǎng)方形。由于你的庫非常流行,你的一些用戶使用Resizable接口創(chuàng)建了他們自己感興趣的實(shí)現(xiàn),比如橢圓。
發(fā)布API幾個(gè)月之后,你突然意識(shí)到Resizable接口遺漏了一些功能。比如,如果接口提供一個(gè)setRelativeSize方法,可以接受參數(shù)實(shí)現(xiàn)對(duì)形狀的大小進(jìn)行調(diào)整,那么接口的易用性會(huì)更好。
你會(huì)說這看起來很容易啊:為Resizable接口添加setRelativeSize方法,再更新Square和Rectangle的實(shí)現(xiàn)就好了。不過,事情并非如此簡(jiǎn)單!你要考慮已經(jīng)使用了你接口的用戶,他們已經(jīng)按照自身的需求實(shí)現(xiàn)了Resizable接口,他們?cè)?strong>如何應(yīng)對(duì)這樣的變更呢?
非常不幸,你無法訪問,也無法改動(dòng)他們實(shí)現(xiàn)了Resizable接口的類。這也是Java庫的設(shè)計(jì)者需要改進(jìn)Java API時(shí)面對(duì)的問題。
初始版本的API
public interface Resizable extends Drawable{int getWidth();int getHeight();void setWidth(int width);void setHeight(int height);void setAbsoluteSize(int width, int height); }用戶實(shí)現(xiàn)
用戶根據(jù)自身的需求實(shí)現(xiàn)了Resizable接口,創(chuàng)建了Ellipse類:
public class Ellipse implements Resizable {…}他實(shí)現(xiàn)了一個(gè)處理各種Resizable形狀(包括Ellipse)的游戲:
public class Game{public static void main(String...args){List<Resizable> resizableShapes =Arrays.asList(new Square(), new Rectangle(), new Ellipse());Utils.paint(resizableShapes);} } public class Utils{public static void paint(List<Resizable> l){l.forEach(r -> {r.setAbsoluteSize(42, 42);r.draw();});} }第二版API
庫上線使用幾個(gè)月之后,你收到很多請(qǐng)求,要求你更新Resizable的實(shí)現(xiàn),讓Square、Rectangle以及其他的形狀都能支持setRelativeSize方法。為了滿足這些新的需求,你發(fā)布了第二版API
public interface Resizable {//...void setRelativeSize(int wFactor, int hFactor); }用戶面臨的窘境
對(duì)Resizable接口的更新導(dǎo)致了一系列的問題。首先,接口現(xiàn)在要求它所有的實(shí)現(xiàn)類添加setRelativeSize方法的實(shí)現(xiàn)。但是用戶最初實(shí)現(xiàn)的Ellipse類并未包含setRelativeSize方法。向接口添加新方法是二進(jìn)制兼容的,這意味著如果不重新編譯該類,即使不實(shí)現(xiàn)新的方法,現(xiàn)有類的實(shí)現(xiàn)依舊可以運(yùn)行。不過,用戶可能修改他的游戲,在他的Utils.paint方法中調(diào)用setRelativeSize方法,因?yàn)閜aint方法接受一個(gè)Resizable對(duì)象列表作為參數(shù)。如果傳遞的是一個(gè)Ellipse對(duì)象,程序就會(huì)拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤,因?yàn)樗⑽磳?shí)現(xiàn)setRelativeSize方法:
Exception in thread "main" java.lang.AbstractMethodError:com.lun.c09.Ellipse.setRelativeSize(II)V其次,如果用戶試圖重新編譯整個(gè)應(yīng)用(包括Ellipse類),他會(huì)遭遇下面的編譯錯(cuò)誤:
com/lun/c09/Ellipse.java:6: error: Ellipse is not abstract and does not override abstract method setRelativeSize(int,int) in Resizable最后,更新已發(fā)布API會(huì)導(dǎo)致后向兼容性問題。這就是為什么對(duì)現(xiàn)存API的演進(jìn),比如官方發(fā)布的Java Collection API,會(huì)給用戶帶來麻煩。當(dāng)然,還有其他方式能夠?qū)崿F(xiàn)對(duì)API的改進(jìn),但是都不是明智的選擇。比如,你可以為你的API創(chuàng)建不同的發(fā)布版本,同時(shí)維護(hù)老版本和新版本,但這是非常費(fèi)時(shí)費(fèi)力的,原因如下。
不同類型的兼容性:二進(jìn)制、源代碼和函數(shù)行為
變更對(duì)Java程序的影響大體可以分成三種類型的兼容性,分別是:二進(jìn)制級(jí)的兼容、源代碼級(jí)的兼容,以及函數(shù)行為的兼容。
二進(jìn)制級(jí)的兼容性表示現(xiàn)有的二進(jìn)制執(zhí)行文件能無縫持續(xù)鏈接(包括驗(yàn)證、準(zhǔn)備和解析)和運(yùn)行。比如,為接口添加一個(gè)方法就是二進(jìn)制級(jí)的兼容,這種方式下,如果新添加的方法不被調(diào)用,接口已經(jīng)實(shí)現(xiàn)的方法可以繼續(xù)運(yùn)行,不會(huì)出現(xiàn)錯(cuò)誤。
源代碼級(jí)的兼容性表示引入變化之后,現(xiàn)有的程序依然能成功編譯通過。比如,向接口添加新的方法就不是源碼級(jí)的兼容,因?yàn)檫z留代碼并沒有實(shí)現(xiàn)新引入的方法,所以它們無法順利通過編譯。
函數(shù)行為的兼容性表示變更發(fā)生之后,程序接受同樣的輸入能得到同樣的結(jié)果。比如,為接口添加新的方法就是函數(shù)行為兼容的,因?yàn)樾绿砑拥姆椒ㄔ诔绦蛑胁⑽幢徽{(diào)用(抑或該接口在實(shí)現(xiàn)中被覆蓋了)。
概述默認(rèn)方法
默認(rèn)方法由default修飾符修飾,并像類中聲明的其他方法一樣包含方法體。比如,你可以像下面這樣在集合庫中定義一個(gè)名為Sized的接口,在其中定義一個(gè)抽象方法size,以及一個(gè)默認(rèn)方法isEmpty:
public interface Sized {int size();default boolean isEmpty() {return size() == 0;} }這樣任何一個(gè)實(shí)現(xiàn)了Sized接口的類都會(huì)自動(dòng)繼承isEmpty的實(shí)現(xiàn)。因此,向提供了默認(rèn)實(shí)現(xiàn)的接口添加方法就 不是 源碼兼容的。
現(xiàn)在,我們回顧一下最初的例子,那個(gè)Java畫圖類庫和你的游戲程序。具體來說,為了以兼容的方式改進(jìn)這個(gè)庫(即使用該庫的用戶不需要修改他們實(shí)現(xiàn)了Resizable的類),可以使用默認(rèn)方法,提供setRelativeSize的默認(rèn)實(shí)現(xiàn):
default void setRelativeSize(int wFactor, int hFactor){setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor); }默認(rèn)方法在Java 8的API中已經(jīng)大量地使用了
Collection接口的stream方法就是默認(rèn)方法。List接口的sort方法也是默認(rèn)方法。
函數(shù)式接口,比如Predicate、Function以及Comparator也引入了新的默認(rèn)方法,比如Predicate.and或者Function.andThen
Java 8中的抽象類和抽象接口
那么抽象類和抽象接口之間的區(qū)別是什么呢?它們不都能包含抽象方法和包含方法體的實(shí)現(xiàn)嗎?
- 一個(gè)類只能繼承一個(gè)抽象類,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。
- 一個(gè)抽象類可以通過實(shí)例變量(字段)保存一個(gè)通用狀態(tài),而接口是不能有實(shí)例變量的。
使用默認(rèn)實(shí)現(xiàn)的例子——removeIf
這個(gè)測(cè)驗(yàn)里,假設(shè)你是Java語言和API的一個(gè)負(fù)責(zé)人。你收到了關(guān)于removeIf方法的很多請(qǐng)求,希望能為ArrayList、TreeSet、LinkedList以及其他集合類型添加removeIf方法。removeIf方法的功能是刪除滿足給定謂詞的所有元素。你的任務(wù)是找到添加這個(gè)新方法、優(yōu)化Collection API的最佳途徑。
改進(jìn)Collection API破壞性最大的方式是什么?你可以把removeIf的實(shí)現(xiàn)直接復(fù)制到Collection API的每個(gè)實(shí)體類中,但這種做法實(shí)際是在對(duì)Java界的犯罪。還有其他的方式嗎?
所有的Collection類都實(shí)現(xiàn)了一個(gè)名為java.util.Collection的接口。太好了,那么我們可以在這里添加一個(gè)方法?是的!你只需要牢記,默認(rèn)方法是一種以源碼兼容方式向接口內(nèi)添加實(shí)現(xiàn)的方法。這樣實(shí)現(xiàn)Collction的所有類(包括并不隸屬Collection API的用戶擴(kuò)展類)都能使用removeIf的默認(rèn)實(shí)現(xiàn)。removeIf的代碼實(shí)現(xiàn)如下(它實(shí)際就是Java 8 Collection API的實(shí)現(xiàn))。
default boolean removeIf(Predicate<? super E> filter) {boolean removed = false;Iterator<E> each = iterator();while(each.hasNext()) {if(filter.test(each.next())) {each.remove();removed = true;}}return removed; }默認(rèn)方法的使用模式
可選方法
類實(shí)現(xiàn)了接口,不過卻刻意地將一些方法的實(shí)現(xiàn)留白。我們以Iterator接口為例來說。Iterator接口定義了hasNext、next,還定義了remove方法。Java 8之前,由于用戶通常不會(huì)使用該方法,remove方法常被忽略。因此,實(shí)現(xiàn)Interator接口的類通常會(huì)為remove方法放置一個(gè)空的實(shí)現(xiàn),這些都是些毫無用處的模板代碼。
采用默認(rèn)方法之后,你可以為這種類型的方法提供一個(gè)默認(rèn)的實(shí)現(xiàn),這樣實(shí)體類就無需在自己的實(shí)現(xiàn)中顯式地提供一個(gè)空方法。比如,在Java 8中,Iterator接口就為remove方法提供了一個(gè)默認(rèn)實(shí)現(xiàn),如下所示:
interface Iterator<T> {boolean hasNext();T next();default void remove() {throw new UnsupportedOperationException();} }通過這種方式,你可以減少無效的模板代碼。實(shí)現(xiàn)Iterator接口的每一個(gè)類都不需要再聲明一個(gè)空的remove方法了,因?yàn)樗F(xiàn)在已經(jīng)有一個(gè)默認(rèn)的實(shí)現(xiàn)。
行為的多繼承
默認(rèn)方法讓之前無法想象的事兒以一種優(yōu)雅的方式得以實(shí)現(xiàn),即行為的多繼承。這是一種讓類從多個(gè)來源重用代碼的能力
Java的類只能繼承單一的類,但是一個(gè)類可以實(shí)現(xiàn)多接口。下面是Java API中對(duì)ArrayList類的定義:
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable,Serializable, Iterable<E>, Collection<E> { }類型的多繼承
這個(gè)例子中ArrayList繼承了一個(gè)類,實(shí)現(xiàn)了六個(gè)接口。因此ArrayList實(shí)際是七個(gè)類型的直接子類,分別是:AbstractList、List、RandomAccess、Cloneable、Serializable、Iterable和Collection。所以,在某種程度上,我們?cè)缇陀辛祟愋偷亩嗬^承。
由于Java 8中接口方法可以包含實(shí)現(xiàn),類可以從多個(gè)接口中繼承它們的行為(即實(shí)現(xiàn)的代碼)。
保持接口的精致性和正交性能幫助你在現(xiàn)有的代碼基上最大程度地實(shí)現(xiàn)代碼復(fù)用和行為組合。
利用正交方法的精簡(jiǎn)接口
假設(shè)你需要為你正在創(chuàng)建的游戲定義多個(gè)具有不同特質(zhì)的形狀。有的形狀需要調(diào)整大小,但是不需要有旋轉(zhuǎn)的功能;有的需要能旋轉(zhuǎn)和移動(dòng),但是不需要調(diào)整大小。
你可以定義一個(gè)單獨(dú)的Rotatable接口,并提供兩個(gè)抽象方法setRotationAngle和getRotationAngle,如下所示
public interface Rotatable {void setRotationAngle(int angleInDegrees);int getRotationAngle();default void rotateBy(int angleInDegrees){setRotationAngle((getRotationAngle () + angle) % 360);} }這種方式和模板設(shè)計(jì)模式有些相似,都是以其他方法需要實(shí)現(xiàn)的方法定義好框架算法。
現(xiàn)在,實(shí)現(xiàn)了Rotatable的所有類都需要提供setRotationAngle和getRotationAngle的實(shí)現(xiàn),但與此同時(shí)它們也會(huì)天然地繼承rotateBy的默認(rèn)實(shí)現(xiàn)。
類似地,你可以定義之前看到的兩個(gè)接口Moveable和Resizable。它們都包含了默認(rèn)實(shí)現(xiàn)。下面是Moveable的代碼:
public interface Moveable {int getX();int getY();void setX(int x);void setY(int y);default void moveHorizontally(int distance){setX(getX() + distance);}default void moveVertically(int distance){setY(getY() + distance);} }public interface Resizable {int getWidth();int getHeight();void setWidth(int width);void setHeight(int height);void setAbsoluteSize(int width, int height);default void setRelativeSize(int wFactor, int hFactor){setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);} }組合接口
通過組合這些接口,你現(xiàn)在可以為你的游戲創(chuàng)建不同的實(shí)體類。
public class Monster implements Rotatable, Moveable, Resizable { … }public class Sun implements Moveable, Rotatable { … }像你的游戲代碼那樣使用默認(rèn)實(shí)現(xiàn)來定義簡(jiǎn)單的接口還有另一個(gè)好處。假設(shè)你需要修改moveVertically的實(shí)現(xiàn),讓它更高效地運(yùn)行。你可以在Moveable接口內(nèi)直接修改它的實(shí)現(xiàn),所有實(shí)現(xiàn)該接口的類會(huì)自動(dòng)繼承新的代碼
繼承不應(yīng)該成為你一談到代碼復(fù)用就試圖倚靠的萬金油
比如,從一個(gè)擁有100個(gè)方法及字段的類進(jìn)行繼承就不是個(gè)好主意,因?yàn)檫@其實(shí)會(huì)引入不必要的復(fù)雜性。你完全可以使用代理有效地規(guī)避這種窘境,即創(chuàng)建一個(gè)方法通過該類的成員變量直接調(diào)用該類的方法。
這就是為什么有的時(shí)候我們發(fā)現(xiàn)有些類被刻意地聲明為final類型:聲明為final的類不能被其他的類繼承,避免發(fā)生這樣的反模式,防止核心代碼的功能被污染。注意,有的時(shí)候聲明為final的類都會(huì)有其不同的原因,比如,String類被聲明為final,因?yàn)槲覀儾幌M腥藢?duì)這樣的核心功能產(chǎn)生干擾。
這種思想同樣也適用于使用默認(rèn)方法的接口。通過精簡(jiǎn)的接口,你能獲得最有效的組合,因?yàn)槟憧梢灾贿x擇你需要的實(shí)現(xiàn)。
解決沖突的規(guī)則
我們知道Java語言中一個(gè)類只能繼承一個(gè)父類,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。隨著默認(rèn)方法在Java 8中引入,有可能出現(xiàn)一個(gè)類繼承了多個(gè)方法而它們使用的卻是同樣的函數(shù)簽名。
這種情況下,類會(huì)選擇使用哪一個(gè)函數(shù)?在實(shí)際情況中,像這樣的沖突可能極少發(fā)生,但是一旦發(fā)生這樣的狀況,必須要有一套規(guī)則來確定按照什么樣的約定處理這些沖突。
public interface A {default void hello() {System.out.println("Hello from A");} } public interface B extends A {default void hello() {System.out.println("Hello from B");} } public class C implements B, A {public static void main(String... args) {new C().hello();} }解決問題的三條規(guī)則
如果一個(gè)類使用相同的函數(shù)簽名從多個(gè)地方(比如另一個(gè)類或接口)繼承了方法,通過三條規(guī)則可以進(jìn)行判斷。
類中的方法優(yōu)先級(jí)最高。類或父類中聲明的方法的優(yōu)先級(jí)高于任何聲明為默認(rèn)方法的優(yōu)先級(jí)。
如果無法依據(jù)第一條進(jìn)行判斷,那么子接口的優(yōu)先級(jí)更高:函數(shù)簽名相同時(shí),優(yōu)先選擇擁有最具體實(shí)現(xiàn)的默認(rèn)方法的接口,即如果B繼承了A,那么B就比A更加具體。
最后,如果還是無法判斷,繼承了多個(gè)接口的類必須通過顯式覆蓋和調(diào)用期望的方法。
選擇提供了最具體實(shí)現(xiàn)的默認(rèn)方法的接口
例子中C類同時(shí)實(shí)現(xiàn)了B接口和A接口,而這兩個(gè)接口恰巧又都定義了名為hello的默認(rèn)方法。另外,B繼承自A。
編譯器會(huì)使用聲明的哪一個(gè)hello方法呢?按照規(guī)則(2),應(yīng)該選擇的是提供了最具體實(shí)現(xiàn)的默認(rèn)方法的接口。由于B比A更具體,所以應(yīng)該選擇B的hello方法。所以,程序會(huì)打印輸出“Hello from B”。
如果C像下面這樣繼承自D
依據(jù)規(guī)則(1),類中聲明的方法具有更高的優(yōu)先級(jí)。D并未覆蓋hello方法,可是它實(shí)現(xiàn)了接口A。所以它就擁有了接口A的默認(rèn)方法。
規(guī)則(2)說如果類或者父類沒有對(duì)應(yīng)的方法,那么就應(yīng)該選擇提供了最具體實(shí)現(xiàn)的接口中的方法。因此,編譯器會(huì)在接口A和接口B的hello方法之間做選擇。由于B更加具體,所以程序會(huì)再次打印輸出“Hello from B”。
在這個(gè)測(cè)驗(yàn)中繼續(xù)復(fù)用之前的例子,唯一的不同在于D現(xiàn)在顯式地覆蓋了從A接口中繼承的hello方法。輸出會(huì)是什么呢?
public class D implements A{void hello(){System.out.println("Hello from D");} }public class C extends D implements B, A {public static void main(String... args) {new C().hello();} }由于依據(jù)規(guī)則(1),父類中聲明的方法具有更高的優(yōu)先級(jí),所以程序會(huì)打印輸出“Hello from D”。
注意,D的聲明如下:
public abstract class D implements A {public abstract void hello(); }這樣的結(jié)果是,雖然在結(jié)構(gòu)上,其他的地方已經(jīng)聲明了默認(rèn)方法的實(shí)現(xiàn),C還是必須提供自己的hello方法
沖突及如何顯式地消除歧義
假設(shè)B不再繼承A
public interface A {void hello() {System.out.println("Hello from A");} } public interface B {void hello() {System.out.println("Hello from B");} } public class C implements B, A { }這時(shí)規(guī)則(2)就無法進(jìn)行判斷了,因?yàn)閺木幾g器的角度看沒有哪一個(gè)接口的實(shí)現(xiàn)更加具體,兩個(gè)都差不多。A接口和B接口的hello方法都是有效的選項(xiàng)。所以,Java編譯器這時(shí)就會(huì)拋出一個(gè)編譯錯(cuò)誤,因?yàn)?strong>它無法判斷哪一個(gè)方法更合適:“Error: class C inherits unrelated defaults for hello()from types B and A.”
沖突的解決
解決這種兩個(gè)可能的有效方法之間的沖突,沒有太多方案;你只能顯式地決定你希望在C中使用哪一個(gè)方法。
為了達(dá)到這個(gè)目的,你可以覆蓋類C中的hello方法,在它的方法體內(nèi)顯式地調(diào)用你希望調(diào)用的方法。
Java 8中引入了一種新的語法X.super.m(…),其中X是你希望調(diào)用的m方法所在的父接口。舉例來說,如果你希望C使用來自于B的默認(rèn)方法,它的調(diào)用方式看起來就如下所示:
public class C implements B, A {void hello(){B.super.hello();} }假設(shè)接口A和B的聲明如下所示:
public interface A{default Number getNumber(){return 10;} } public interface B{default Integer getNumber(){return 42;} }類C的聲明如下:
public class C implements B, A {public static void main(String... args) {System.out.println(new C().getNumber());} }輸出什么呢?
答案:類C無法判斷A或者B到底哪一個(gè)更加具體。這就是類C無法通過編譯的原因。
菱形繼承問題
public interface A{default void hello(){System.out.println("Hello from A");} }public interface B extends A { }public interface C extends A { }public class D implements B, C {public static void main(String... args) {new D().hello();} }類D中的默認(rèn)方法到底繼承自什么地方 ——源自B的默認(rèn)方法,還是源自C的默認(rèn)方法?實(shí)際上只有一個(gè)方法聲明可以選擇。只有A聲明了一個(gè)默認(rèn)方法。由于這個(gè)接口是D的父接口,代碼會(huì)打印輸出“Hello from A”。
如果B中也提供了一個(gè)默認(rèn)的hello方法,并且函數(shù)簽名跟A中的方法也完全一致,
根據(jù)規(guī)則(2),編譯器會(huì)選擇提供了更具體實(shí)現(xiàn)的接口中的方法。由于B比A更加具體,所以編譯器會(huì)選擇B中聲明的默認(rèn)方法。
如果B和C都使用相同的函數(shù)簽名聲明了hello方法,就會(huì)出現(xiàn)沖突,你需要顯式地指定使用哪個(gè)方法。
如果你在C接口中添加一個(gè)抽象的hello方法(這次添加的不是一個(gè)默認(rèn)方法),會(huì)發(fā)生什么情況呢?
public interface C extends A {void hello(); }這個(gè)新添加到C接口中的抽象方法hello比由接口A繼承而來的hello方法擁有更高的優(yōu)先級(jí),因?yàn)镃接口更加具體。因此,類D現(xiàn)在需要為hello顯式地添加實(shí)現(xiàn),否則該程序無法通過編譯。
小結(jié)
- Java 8中的接口可以通過默認(rèn)方法和靜態(tài)方法提供方法的代碼實(shí)現(xiàn)。
- 默認(rèn)方法的開頭以關(guān)鍵字default修飾,方法體與常規(guī)的類方法相同。
- 向發(fā)布的接口添加抽象方法不是源碼兼容的。
- 默認(rèn)方法的出現(xiàn)能幫助庫的設(shè)計(jì)者以后向兼容的方式演進(jìn)API。
- 默認(rèn)方法可以用于創(chuàng)建可選方法和行為的多繼承。
- 我們有辦法解決由于一個(gè)類從多個(gè)接口中繼承了擁有相同函數(shù)簽名的方法而導(dǎo)致的沖突。
- 類或者父類中聲明的方法的優(yōu)先級(jí)高于任何默認(rèn)方法。如果前一條無法解決沖突,那就選擇同函數(shù)簽名的方法中實(shí)現(xiàn)得最具體的那個(gè)接口的方法。
- 兩個(gè)默認(rèn)方法都同樣具體時(shí),你需要在類中覆蓋該方法,顯式地選擇使用哪個(gè)接口中提供的默認(rèn)方法。
總結(jié)
以上是生活随笔為你收集整理的《Java8实战》笔记(09):默认方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Python Cookbook 3rd
- 下一篇: 《Java8实战》笔记(06):用流收集