1. 程式人生 > >Java8新特性整理之介面的預設方法

Java8新特性整理之介面的預設方法

Java8新特性整理之介面的預設方法

預設方法

預設方法由default修飾符修飾,並像類中宣告的其他方法一樣包含方法體。

比如,你可以像下面這樣在集合庫中定義一個名為
Sized的介面,在其中定義一個抽象方法size,以及一個預設方法isEmpty

public interface Sized { 
    int size(); 
    default boolean isEmpty() {  
        return size() == 0; 
    } 
} 

這樣任何一個實現了Sized介面的類都會自動繼承isEmpty預設方法的實現。

為什麼要有這個特性?

首先,之前的介面是個雙刃劍,好處是面向抽象而不是面向具體程式設計,缺陷是,當需要修改介面時候,需要修改全部實現該介面的類,目前的java8之前的集合框架沒有foreach方法,通常能想到的解決辦法是在JDK裡給相關的介面新增新的方法及實現。然而,對於已經發布的版本,是沒法在給介面新增新方法的同時不影響已有的實現。所以引進的預設方法。他們的目的是為了解決介面的修改與現有的實現不相容的問題。

那麼抽象類和抽象介面之間的區別是什麼呢?

首先,一個類只能繼承一個抽象類,但是一個類可以實現多個介面。

其次,一個抽象類可以通過例項變數(欄位)儲存一個通用狀態,而介面是不能有例項變數的。

使用預設方法的兩種用例

可選方法

你很可能也碰到過這種情況,類實現了介面,不過卻刻意地將一些方法的實現留白。

我們以Iterator介面為例來說。Iterator介面定義了hasNext、next,還定義了remove方法。

Java 8之前,由於使用者通常不會使用該方法,remove方法常被忽略。因此,實現Iterator介面的類通常會為remove方法放置一個空的實現,這些都是些毫無用處的模板程式碼。

採用預設方法之後,你可以為這種型別的方法提供一個預設的實現,這樣實體類就無需在自己的實現中顯式地提供一個空方法。

比如,在Java 8中,Iterator介面就為remove方法提供了
一個預設實現,如下所示:

interface Iterator<T> { 
    boolean hasNext(); 
    T next(); 
    default void remove() { 
        throw new UnsupportedOperationException(); 
    } 
}

通過這種方式,你可以減少無效的模板程式碼。實現Iterator介面的每一個類都不需要再宣告一個空的remove方法了,因為它現在已經有一個預設的實現。

行為的多繼承

這是一種讓類從多個來源重用程式碼的能力。

如下所示程式碼:

A.java

public interface A {

  void doSomething();

  default void hello() {
    System.out.println("hello world from interface A");
  }

  default void foo() {
    System.out.println("foo from interface A");
  }
}

B.java

interface B extends A {
  default void hello() {
    System.out.println("hello world from interface B");
  }
}

C.java

public class C implements A, B{
    @Override
    public void doSomething() {
        System.out.println("c object need do something");
    }

    public static void main(String[] args) {
        A obj = new C();
        obj.hello();    //呼叫B的預設方法
        obj.foo();    //呼叫A的預設方法
        obj.doSomething();
    }
}

輸出:

hello world from interface B
foo from interface A
c object need do something

obj.hello()呼叫的是B介面中的預設方法而不是A介面的預設方法。

為什麼會這樣呢?這就引出預設方法中解決衝突的原則。

如果一個類使用相同的函式簽名從多個地方(比如另一個類或介面)繼承了方法,通過三條規則可以進行判斷:

1.類中的方法優先順序最高,類或父類中宣告的方法的優先順序高於任何宣告為預設方法的優先順序。

2.如果無法依據第一條進行判斷,那麼子介面的優先順序更高:函式簽名相同時,優先選擇擁有最具體實現的預設方法的介面,即如果B繼承了A,那麼B就比A更加具體。

3.最後,如果還是無法判斷,繼承了多個介面的類必須通過顯示覆蓋和呼叫期望的方法,顯示地選擇使用哪一個預設方法的實現。

再來看一個栗子:

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 { } 

這時規則(2)就無法進行判斷了,因為從編譯器的角度看沒有哪一個介面的實現更加具體,兩個都差不多。

A介面和B介面的hello方法都是有效的選項。所以,Java編譯器這時就會丟擲一個編譯錯誤,因為它無法判斷哪一個方法更合適:“Error: class C inherits unrelated defaults for hello()
from types B and A.”

如果你希望C使用來自於B的預設方法,它的呼叫方式看起來就如下所示

public class C implements B, A { 
    void hello(){ 
        B.super.hello(); 
    } 
} 

顯式地選擇呼叫介面B中的方法 。

另外也可以在介面中宣告靜態方法

  static void statichello() {
        System.out.println("statichello world from interface B");
    }

Java 8中的介面可以通過預設方法和靜態方法提供方法的程式碼實現。