Java 8 默認方法和多繼承

Java 8 默認方法和多繼承

以前經常談論的Java對比c++的一個優勢是Java中沒有多繼承的問題。 因爲Java中子類只能繼承(extends)單個父類, 儘管可以實現(implements)多個接口,但是接口中只有抽象方法,方法體是空的,沒有具體的方法實現,不會有方法衝突的問題。

這些都是久遠的說法了,自從今年Java 8發佈後, 接口中也可以定義方法了(default method)。 之所以打破以前的設計在接口中
增加具體的方法, 是爲了既有的成千上萬的Java類庫的類增加新的功能, 且不必對這些類重新進行設計。 比如, 只需在Collection接口中
增加default Stream stream(), 相應的Set和List接口以及它們的子類都包含此的方法, 不必爲每個子類都重新copy這個方法。

這是一個折衷的設計,帶來的問題就是爲Java引入了多繼承的問題。 我們知道, 接口可以繼承接口, 類可以繼承類和實現接口。 一旦繼承的類和實現的接口中有
相同簽名的方法, 會出現什麼樣的狀況呢? 本文將探討各種情況的多繼承, 以便能清楚的理解Java多繼承的規則。

接口繼承多個父接口

假定有三個接口Interface A, Interface B, Interface C, 繼承關係如下:

+---------------+         +------------+
|  Interface A  |         |Interface B |
+-----------^---+         +---^--------+
            |                 |         
            |                 |         
            |                 |         
            +-+------------+--+         
              | Interface C|            
              +------------+

A,B擁有相同簽名的默認方法default String say(String name), 如果接口C沒有override這個方法, 則編譯出錯。

   interface A {
    default String say(String name) {
        return "hello " + name;
    }
}
interface B {
    default String say(String name) {
        return "hi " + name;
    }
}
interface C extends A,B{

}

錯誤信息:

C:\Lambda\src>javac -J-Duser.country=US com\colobu\lambda\chap
ter3\MultipleInheritance1.java
com\colobu\lambda\chapter3\MultipleInheritance1.java:17: error: interface C inherits unrelated defaults for say(String) from types A and B
        static interface C extends A,B{
               ^
1 error

我們可以在子接口C中覆蓋override這個方法, 這樣編譯就不會出錯了:

   interface C extends A,B{
    default String say(String name) {
        return "greet " + name;
    }
}

注意方法簽名不包括方法的返回值, 也就是僅僅返回值不同的兩個方法的簽名也是相同的。
下面的代碼編譯不會出錯,因爲A和B的默認方法不同, C隱式繼承了兩個默認方法。

interface A {
    default void say(int name) {

    }
}
interface B {
    default void say(String name) {

    }
}
interface C extends A,B{

}

但是有的情況下即使是不同簽名的方法也是很難分辨的:

interface A {
    default void say(int a) {
        System.out.println("A");
    }
}
interface B {
    default void say(short a) {
        System.out.println("B");
    }
}
interface C extends A,B{

}
static class D implements C {

}
public static void main(String[] args) {
    D d = new D();
    byte a = 1;
    d.say(a); //B
}

Java會選擇最適合的方法, 請參看Java規範 15.12.2.5

接口多層繼承

下面看一下多層繼承的問題。 繼承關係如下圖, A2繼承A1, C繼承A2。

+---------------+ 
|  Interface A1 | 
+--------+------+ 
         |        
         |        
         |        
+--------+------+ 
|  Interface A2 | 
+-------+-------+ 
        |         
        |         
        |         
+-------+--------+
|   Interface C  |
+----------------+

基於我們以前對類繼承的認識, 很容易知道C會繼承A2的默認方法,包括直接定義的默認方法, 覆蓋的默認方法,以及隱式繼承於A1接口的默認方法。

interface A {
    default void say(int a) {
        System.out.println("A");
    }

    default void run() {
        System.out.println("A.run");
    }
}

interface B extends A{
    default void say(int a) {
        System.out.println("B");
    }

    default void play() {
        System.out.println("B.play");
    }
}

interface C extends A,B{

}

多層多繼承

上面一個例子還是單繼承的例子, 如果如下圖的多繼承呢?

+---------------+                          
|  Interface A1 |                          
+--------+------+                          
         |                                 
         |                                 
         |                                 
+--------+------+         +---------------+
|  Interface A2 |         |  Interface B  |
+-------+-------+         +---------+-----+
        |       +---------+---------^      
        |       |                          
        |       |                          
+-------+-------++                         
|   Interface C  |                         
+----------------+

如果A2和B擁有相同簽名的方法,這和第一個例子一樣。 如果不想編譯出錯,可以覆蓋父接口的默認方法,還可以調用指定父接口的默認方法:

interface A1 {
    default void say(int a) {
        System.out.println("A1");
    }
}

interface A2 extends A1 {

}

interface B {
    default void say(int a) {
        System.out.println("B");
    }
}

interface C extends A2,B{
    default void say(int a) {
        B.super.say(a);
    }
}

更復雜的多層多繼承

 +--------------+              
 | Interface A1 |              
 +------+------++              
        |      ^+-------+      
        |               |      
+-------+-------+       |      
|  Interface A2 |       |      
+------------+--+       |      
             ^--++      |      
                 |      |      
              +--+------+-----+
              |  Interface C  |
              +---------------+

接口A2繼承A1, 接口C繼承A2和A1。 代碼如下,

interface A1 {
    default void say() {
        System.out.println("A1");
    }
}
interface A2 extends A1 {
    default void say() {
        System.out.println("A2");
    }
}
interface C extends A2,A1{

}
static class D implements C {

}
public static void main(String[] args) {
    D d = new D();
    d.say();
}

以上代碼不會編譯出錯,運行輸出A2。
可以看到接口C會隱式繼承子接口的方法, 也就是子接口A2的默認方法。

類繼承

如果繼承關係類型全部是類, 那麼由於類依然是單繼承的, 不會有多繼承的問題。

類和接口混雜

我們把第一個例子中的其中一個接口換成類,會出現什麼現象呢。

+-------------+       +-----------+
| Interface A |       |  Class B  |
+-----------+-+       +-----+-----+
            ^-+    +--+-----^      
              |    |               
          +---+----+-+             
          |  Class C |             
          +----------+

以下代碼不會編譯出錯:

interface A {
    default void say() {
        System.out.println("A");
    }
}
static class B {
    public void say() {
        System.out.println("B");
    }
}
static class C extends B implements A{

}
public static void main(String[] args) {
    C c = new C();
    c.say(); //B
}

結果輸出B。
可以看出, 子類優先繼承父類的方法, 如果父類沒有相同簽名的方法,才繼承接口的默認方法。

結論

更復雜的繼承關係可以簡化成以上的繼承關係。
根據以上的例子, 可以得出以下的結論:

類優先於接口。 如果一個子類繼承的父類和接口有相同的方法實現。 那麼子類繼承父類的方法
子類型中的方法優先於父類型中的方法。
如果以上條件都不滿足, 則必須顯示覆蓋/實現其方法,或者聲明成abstract。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章