面試者必看:Java8中的默認方法

封面圖

背景

在Java8以前,定義在接口中的全部方法都須要在接口實現類中提供一個實現,若是接口的提供者須要升級接口,添加新的方法,那麼全部的實現類都須要把這個新增的方法實現一遍,若是說全部的實現類可以本身控制的話,那麼還能接受,可是現實狀況是實現類可能不受本身控制。好比說Java中的集合框架中的List接口添加一個方法,那麼Apache Commons這種框架就會很難受,必須修改全部實現了List的實現類面試


如今的接口有哪些不便

  1. 向已經發布的接口中添加新的方法是問題的根源,一旦接口發生變化,接口的實現者都須要更新代碼,實現新增的接口
  2. 接口中有些方法是可選的,不是全部的實現者都須要實現,這個時候實現類不得不實現一個空的方法,或者是提供一個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之後這些類都被標註成了過時@Deprecatedspring


默認方法的簡介

爲了解決上述問題,在Java8中容許指定接口作默認實現,未指定的接口由實現類去實現。如何標識出接口是默認實現呢?方法前面加上default關鍵字。好比Spring中的WebMvcConfigurersegmentfault

public interface WebMvcConfigurer {

    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    
    省略其餘代碼...
}

從如今看來,可能你們都會有個疑問,默認方法和抽象類有什麼區別呢?架構

    1. 默認方法不能有實例變量,抽象類能夠有
    1. 一個類只能繼承一個抽象類,當時能夠實現多個接口

默認方法的使用場景

  • 可選方法

爲接口提供可選的方法,給出默認實現,這樣實現類就不用顯示的去提供一個空的方法;這種場景剛纔咱們在上面已經說到了,spring中有大量的例子框架

  • 實現多繼承

繼承是面向對象的特性之一,在Java中一直以來都是單繼承的原則,Java8中默認方法爲實現多繼承提供了可能(因爲接口中不能有實例對象,因此可以抽象的到接口中的行爲通常都是比較小的模塊);從我的的經從來看,作遊戲是訓練本身面向對象思惟的最好方式(之後有機會分享一下小遊戲的製做),由於如今大部分學Java的同窗學完Java基礎後就直接進入JavaWeb的學習,整合各類框架,只能在通用的三層架構(Controller、Service、Dao)中寫本身的邏輯。ide

相信不少人在都作個坦克大戰的遊戲,若是用Java8中的默認方法如何設計好多繼承呢?
類設計圖學習

這裏咱們舉個簡單的例子,定義了三個接口:spa

  1. Moveable:容許移動的物體,把移動的邏輯放入到這個接口中的默認方法
  2. Attackable: 容許攻擊的物體,把攻擊的通用邏輯放入到默認方法,不通用的邏輯經過模板方法給實現類處理
  3. Location: 獲取物體的座標

經過這些接口的組合方式,咱們就能夠爲遊戲建立不一樣的實現類,好比說坦克、草地、牆壁...設計


解決默認方法衝突規則(面試者必看)

經過上面的例子,咱們體驗的默認方法給咱們帶來了多繼承的便利,可是讓咱們思考下,若是出現了不一樣的類出現的相同簽名的默認方法,實際在運行的時候應該如何選擇呢?客官不慌,有辦法的3d

    1. 類中的方法優先級最高。若是類或者父類(抽象類也OK)中聲明瞭相同簽名的方法,那麼優先級最高
    1. 若是第一條沒法肯定,那麼最具體的的實現的默認方法;很繞,舉例子:B接口繼承了A,B就更加具體,那麼B中的方法優先級最高
    1. 若是上面兩個都沒法判斷,那麼編譯會報錯,須要在實現接口,而後手動顯式調用
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來修飾
  • 默認方法的使用場景可選方法和多繼承
  • 三個原則解決相同簽名的默認方法衝突問題
相關文章
相關標籤/搜索