【Java】Java8新特性之介面預設方法與靜態方法
一、介面預設方法
預設方法是在介面中的方法簽名前加上了default
關鍵字的實現方法
- 程式碼示例如下:
1 public class TestDefaultMethod { 2 public static void main(String[] args) { 3 ClasA a = new ClasA(); 4 a.foo(); 5 } 6 } 7 8 class ClasA implements InterfaceA {} 9 10 interface InterfaceA { 11 default voidfoo(){ 12 System.out.println("InterfaceA foo"); 13 } 14 }
- 為什麼要有預設方法
在 java 8 之前,介面與其實現類之間的耦合度太高了(tightly coupled),當需要為一個介面新增方法時,所有的實現類都必須隨之修改。預設方法解決了這個問題,它可以為介面新增新的方法,而不會破壞已有的介面的實現。這在 lambda 表示式作為 java 8 語言的重要特性而出現之際,為升級舊介面且保持向後相容(backward compatibility)提供了途徑。
二、預設方法的繼承
和其它方法一樣,介面預設方法也可以被繼承。
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 } 6 7 interface InterfaceB extends InterfaceA { 8 } 9 10 interface InterfaceC extends InterfaceA { 11 @Override 12 default void foo() { 13 System.out.println("InterfaceC foo");14 } 15 } 16 17 interface InterfaceD extends InterfaceA { 18 @Override 19 void foo(); 20 } 21 22 public class Test { 23 public static void main(String[] args) { 24 new InterfaceB() {}.foo(); // 列印:“InterfaceA foo” 25 new InterfaceC() {}.foo(); // 列印:“InterfaceC foo” 26 new InterfaceD() { 27 @Override 28 public void foo() { 29 System.out.println("InterfaceD foo"); 30 } 31 }.foo(); // 列印:“InterfaceD foo” 32 33 // 或者使用 lambda 表示式 34 ((InterfaceD) () -> System.out.println("InterfaceD foo")).foo(); 35 } 36 }
介面預設方法的繼承分三種情況(分別對應上面的InterfaceB
介面、InterfaceC
介面和InterfaceD
介面):
-
不覆寫預設方法,直接從父介面中獲取方法的預設實現。
-
覆寫預設方法,這跟類與類之間的覆寫規則相類似。
-
覆寫預設方法並將它重新宣告為抽象方法,這樣新介面的子類必須再次覆寫並實現這個抽象方法。
介面繼承行為發生衝突時的解決規則
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 } 6 7 interface InterfaceB extends InterfaceA { 8 @Override 9 default void foo() { 10 System.out.println("InterfaceB foo"); 11 } 12 } 13 14 // 正確 15 class ClassA implements InterfaceA, InterfaceB { 16 } 17 18 class ClassB implements InterfaceA, InterfaceB { 19 @Override 20 public void foo() { 21 // InterfaceA.super.foo(); // 錯誤 22 InterfaceB.super.foo(); 23 } 24 }
當ClassA
類多實現InterfaceA
介面和InterfaceB
介面時,不會出現方法名歧義的錯誤。當ClassB
類覆寫foo
方法時,無法通過InterfaceA.super.foo();
呼叫InterfaceA
介面的foo
方法。
因為InterfaceB
介面繼承了InterfaceA
介面,那麼InterfaceB
介面一定包含了所有InterfaceA
介面中的欄位方法,因此一個同時實現了InterfaceA
介面和InterfaceB
介面的類與一個只實現了InterfaceB
介面的類完全等價。
介面與抽象類
當介面繼承行為發生衝突時的另一個規則是,類的方法宣告優先於介面預設方法,無論該方法是具體的還是抽象的。
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 6 default void bar() { 7 System.out.println("InterfaceA bar"); 8 } 9 } 10 11 abstract class AbstractClassA { 12 public abstract void foo(); 13 14 public void bar() { 15 System.out.println("AbstractClassA bar"); 16 } 17 } 18 19 class ClassA extends AbstractClassA implements InterfaceA { 20 @Override 21 public void foo() { 22 InterfaceA.super.foo(); 23 } 24 } 25 26 public class Test { 27 public static void main(String[] args) { 28 ClassA classA = new ClassA(); 29 classA.foo(); // 列印:“InterfaceA foo” 30 classA.bar(); // 列印:“AbstractClassA bar” 31 } 32 }
ClassA
類中並不需要手動覆寫bar
方法,因為優先考慮到ClassA
類繼承了的AbstractClassA
抽象類中存在對bar
方法的實現,同樣的因為AbstractClassA
抽象類中的foo
方法是抽象的,所以在ClassA
類中必須實現foo
方法。
三、介面靜態方法
Java 8 還在允許在介面中定義靜態方法
1 interface InterfaceA { 2 default void foo() { 3 printHelloWorld(); 4 } 5 6 static void printHelloWorld() { 7 System.out.println("hello, world"); 8 } 9 } 10 11 public class Test { 12 public static void main(String[] args) { 13 InterfaceA.printHelloWorld(); // 列印:“hello, world” 14 } 15 }
四、其他注意點
-
default
關鍵字只能在介面中使用(以及用在switch
語句的default
分支),不能用在抽象類中。 -
介面預設方法不能覆寫
Object
類的equals
、hashCode
和toString
方法。 -
介面中的靜態方法必須是
public
的,public
修飾符可以省略,static
修飾符不能省略。 -
即使使用了 java 8 的環境,一些 IDE 仍然可能在一些程式碼的實時編譯提示時出現異常的提示(例如無法發現 java 8 的語法錯誤),因此不要過度依賴 IDE。