Java8中的預設方法(面試者必看)
背景
在Java8之前,定義在介面中的所有方法都需要在介面實現類中提供一個實現,如果介面的提供者需要升級介面,新增新的方法,那麼所有的實現類都需要把這個新增的方法實現一遍,如果說所有的實現類能夠自己控制的話,那麼還能接受,但是現實情況是實現類可能不受自己控制。比如說Java中的集合框架中的List介面新增一個方法,那麼Apache Commons這種框架就會很難受,必須修改所有實現了List的實現類
現在的介面有哪些不便
向已經發布的介面中新增新的方法是問題的根源,一旦介面發生變化,介面的實現者都需要更新程式碼,實現新增的介面
介面中有些方法是可選的,不是所有的實現者都需要實現,這個時候實現類不得不實現一個空的方法,或者是提供一個Adapter對介面中所有的方法做空實現,在Spring中我們可看到很多這種例子,比如WebMvcConfigurerAdapter
@Deprecated public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { } 省略其他程式碼... }
在Java8以後這些類都被標註成了過期@Deprecated
預設方法的簡介
為了解決上述問題,在Java8中允許指定介面做預設實現,未指定的介面由實現類去實現。如何標識出介面是預設實現呢?方法前面加上default關鍵字。比如Spring中的WebMvcConfigurer
public interface WebMvcConfigurer { default void configurePathMatch(PathMatchConfigurer configurer) { } default void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } default void configureAsyncSupport(AsyncSupportConfigurer configurer) { } 省略其他程式碼... }
從現在看來,可能大家都會有個疑問,預設方法和抽象類有什麼區別呢?
- 預設方法不能有例項變數,抽象類可以有
- 一個類只能繼承一個抽象類,當時可以實現多個介面
預設方法的使用場景
可選方法
為介面提供可選的方法,給出預設實現,這樣實現類就不用顯示的去提供一個空的方法;這種場景剛才我們在上面已經說到了,spring中有大量的例子
實現多繼承
繼承是面向物件的特性之一,在Java中一直以來都是單繼承的原則,Java8中預設方法為實現多繼承提供了可能(由於介面中不能有例項物件,所以能夠抽象的到介面中的行為一般都是比較小的模組);從個人的經歷來看,做遊戲是訓練自己面向物件思維的最好方式(以後有機會分享一下小遊戲的製作),因為現在大部分學Java的同學學完Java基礎後就直接進入JavaWeb的學習,整合各種框架,只能在通用的三層架構(Controller、Service、Dao)中寫自己的邏輯。
相信很多人在都做個坦克大戰的遊戲,如果用Java8中的預設方法如何設計好多繼承呢?
這裡我們舉個簡單的例子,定義了三個介面:
- Moveable:允許移動的物體,把移動的邏輯放入到這個介面中的預設方法
- Attackable: 允許攻擊的物體,把攻擊的通用邏輯放入到預設方法,不通用的邏輯通過模板方法給實現類處理
- Location: 獲取物體的座標
通過這些介面的組合方式,我們就可以為遊戲建立不同的實現類,比如說坦克、草地、牆壁...
解決預設方法衝突規則(面試者必看)
通過上面的例子,我們體驗的預設方法給我們帶來了多繼承的便利,但是讓我們思考下,如果出現了不同的類出現的相同簽名的預設方法,實際在執行的時候應該如何選擇呢?客官不慌,有辦法的
- 類中的方法優先順序最高。如果類或者父類(抽象類也OK)中聲明瞭相同簽名的方法,那麼優先順序最高
- 如果第一條無法確定,那麼最具體的的實現的預設方法;很繞,舉例子:B介面繼承了A,B就更加具體,那麼B中的方法優先順序最高
- 如果上面兩個都無法判斷,那麼編譯會報錯,需要在實現介面,然後手動顯式呼叫
public class C implements A,B { void pint() { B.supper.print(); // 顯式呼叫 } }
菱形繼承問題
為了說明上面的三個原則,我們直接來看看最複雜的菱形繼承問題
public interface A { default void print(){ System.out.println("Class A"); } } public interface B extend A {} public interface C extend A {} public class D implement B,C { public static void main(String[] args) { new D().print() } }
這種情況下B,C都沒有自己的實現,實際上就只有A有實現,那麼會列印Class A
如果說這個時候把介面B介面改一下
public interface B extends A { default void print(){ System.out.println("Class B"); } }
根據原則(2),B繼承於A,更加具體,所以列印結果應該是B
如果說把介面C修改一下
public interface C extends A { default void print(){ System.out.println("Class C"); } }
這時候我們發現編譯報錯,需要我們自己手動指定,修改D中的程式碼
public class D implements B,C { @Override public void print() { C.super.print(); } public static void main(String[] args) { new D().print(); } }
總結
- Java8中的預設方法需要使用default來修飾
- 預設方法的使用場景可選方法和多繼承
- 三個原則解決相同簽名的預設方法衝突問題
到此這篇關於Java8中的預設方法(面試者必看)的文章就介紹到這了,更多相關Java8 預設方法內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!