Java 接口相信所有學過 Java 的人一定都知道,而且 99% 的人一定都背過這個面試題:Java 接口和抽象類的區別是什么?答案都是什么接口方法不能有實現,都是抽象的,接口的方法都默認為 public 修飾等等之類的,這些在 Java 8 之前是正確的,但是隨著 Java 的發展,它不再是正確的了。
一起來探索 Java 接口的進化之路。
出生:Java 1.0 引入接口
Java 1.0 ,作為 Java 語言的最初版本,于 1996 年發布。在這個版本中,Java 引入了接口(Interface
)這一概念,標志著面向對象編程(OOP)在 Java 中的深入應用。
在 Java 1.0 中,接口被定義為完全抽象的,它具備如下特點:
- 完全抽象:接口被定義為完全抽象,意味著它不能包含任何方法的具體實現。
- 只能包含方法簽名和常量:接口中只能聲明方法簽名(沒有方法體)和公共靜態常量(
public static final
)。這意味著,在接口中定義的所有字段都會自動成為公共、靜態、最終的,而所有方法都被視為公共的和抽象的。 - 沒有構造函數:接口不能被實例化,所以它們不能包含構造函數。
- 多重實現:不同于類的繼承,Java 允許一個類實現多個接口。由于 Java 只允許單繼承,所以接口的出現,為 Java 的多重繼承提供了實現的可能。
- 實現類必須實現所有方法:任何一個實現接口的類都必須實現接口中聲明的所有方法,除非該類被聲明為抽象類。
這個時候定義接口只能定義為如下形式:
public interface MyInterface {String SK_URL = "https://example.com/";String getSeriesUrl();
}
屬性 SK_URL 默認為 public static final
。
Java 1.1 引入內部類,內部類提供了一種強大的機制,它允許將一個類的定義放置在另一個類的定義內部,從而與外部類緊密地綁定在一起。內部類中的匿名內部類,可以用于實現接口,這對接口的使用有著特別的意義,它提高了代碼的封裝性和可維護性。
會爬了:Java 1.5 引入泛型
Java 5 于 2004 年發布,它引入了泛型,泛型的引入對接口的使用產生了深遠的影響。
在引入泛型之前,Java 接口通常處理的是 Object 類型的數據,這意味著任何類型的對象都可以傳遞給這些接口。在實現類中我們需要將 Object 類型的數據轉換為其它具體類型,這種轉換可能會導致 ClassCastException。
引入泛型后,接口可以在定義時就指定具體的類型參數,這使得接口在編譯時就能夠檢查和確認數據類型,從而大大增強了類型安全。同時,泛型允許接口定義通用的模板,例如List<E>
或 Comparator<T>
,這些接口可以被不同類型的數據重用,而不需要為每種數據類型創建新的接口。
下面舉一個簡單的例子來說明,引入 泛型對接口好處。
在 Java 5 引入泛型之前,集合類如 List 接口存儲的元素類型是不確定的,它們默認存儲 Object 類型的對象。這使得集合可以存儲任何類型的對象,但也帶來了類型安全問題。
List list = new ArrayList();
list.add("skjava.com");
list.add(1); // 這是合法的,因為集合可以存儲任何類型的對象// 獲取時需要進行類型轉換
String str = (String) list.get(0);
Integer num = (Integer) list.get(1);
list 可以同時存儲 String 和 Integer。但是,在獲取元素時,我們需要進行顯式的類型轉換。如果在運行時類型不匹配,會拋出 ClassCastException。
引入泛型后,可以在定義集合時指定存儲的元素類型。這提高了類型安全,并減少了運行時的錯誤。
List<String> list = new ArrayList<>();
list.add("skjava.com");
// list.add(1); // 這行代碼會導致編譯錯誤,因為 list 只能存儲字符串// 獲取時不需要進行類型轉換
String str = list.get(0);
list 被定義為僅能存儲 String 類型的 List,當嘗試添加非 String 類型(如 Integer)到該 list 中會在編譯時報錯,而不是在運行時。同時,獲取元素時也不需要顯式的類型轉換,因為編譯器已經確保了所有元素都是 String 類型。
會走路了:Java 8 引入默認方法和靜態方法
Java 8 是一個重大的里程碑,因為它為Java接口引入了兩個重要的新特性:默認方法和靜態方法,這兩個特性讓 Java 接口可以走路了。
在 Java 8 之前,接口中申明的方法必須是抽象的,實現該接口就需要實現該接口的所有方法。我們知道接口的設計是一項巨大的工作,因為如果我們需要在接口中新增一個方法,需要對它的所有實現類都進行修改,如果它的實現類比較少還可以接受,如果實現類比較多則工作量就比較大了。而且有些實現類根本不需要實現該方法,也由于 Java 接口的限制導致它強迫實現該方法。
為了解決這個問題,Java 8 引入了默認方法,默認方法允許在接口中添加具有默認實現的方法,它使得接口可以包含方法的實現,而不僅僅是抽象方法的定義。這一特性可以在不破壞現有實現的前提下向接口添加新方法,使得接口具備了向后兼容的能力。
用法如下:
- 使用
default
關鍵字標識方法,然后方法提供具體實現。
public interface MyInterface {default void print() {System.out.println("我是大明哥");}
}
實現接口的類可以直接使用這些默認方法,或者根據需要重寫它們:
public class MyInterfaceImpl implements MyInterface{@Overridepublic void print() {System.out.println("我是大明哥 2 號");}
}
Java 8 中還允許在接口中定義靜態方法。這些方法與類中的靜態方法一樣,是屬于接口的而不是接口的實例。靜態方法主要用于提供輔助方法,例如根據傳入參數構建接口的實現,或者提供通用的工具方法。
用法:
- 靜態方法使用
static
關鍵字聲明,并且必須提供實現。 - 是通過接口名直接調用靜態方法,不需要接口的實例。
public interface MyInterface {static int add(int a, int b) {return a + b;}
}
add()
是通過MyInterface.add(5, 3)
直接調用,而不需要創建MyInterface
的實例。
Java 引入默認方法和靜態方法大大增強了 Java 接口的能力,提高了 Java 接口的靈活性和功能性。
更多關于接口默認方法和靜態方法,閱讀:Java 8 新特性—接口默認方法和靜態方法
可以跑了:Java 9 引入私有方法
Java 8 為引入了默認方法和靜態方法,提高了 Java 接口的靈活性和功能性。但是這些依然是 public 的,這在某些情況下會導致了代碼重復和封裝性不足的問題。為了解決這個問題,Java 9 引入私有方法。
- 私有方法只能在定義它們的接口內部被訪問。這就意味著接口可以有自己的內部邏輯,而不必將所有邏輯都暴露給實現該接口的類。這樣,接口的設計者可以更好地控制接口的行為,并防止實現類誤用接口內部的代碼,增強了接口的封裝性。
- 由于私有方法只能在接口內部使用,無法在接口的實現類中被濫用,這有助于保持接口的一致性和約定。
在接口中使用私有方法有如下幾個限制:
- 私有方法不能是抽象的。
- 私有方法只能在接口內部使用,無法被接口的實現類或外部類訪問。
- 私有方法不會繼承給接口的子接口,每個接口都必須自己定義自己的私有方法。
- 私有靜態方法可以在其他靜態和非靜態接口方法中使用。
- 私有非靜態方法不能在私有靜態方法內部使用。
下面是私有方法的簡單使用:
public interface MyInterface {private void print() {System.out.println("我是大明哥");}
}
總示例
下面用一個例子來演示上面所有功能。
public interface MyInterface {String NAME = "MyInterface";/*** 抽象方法,所有實現類都要實現* @return*/void abstractMethod();/*** 默認方法*/default void defaultMethod() {//默認方法里可以調用私有方法privateMethod();// 默認方法可以調用靜態私有方法staticPrivateMethod();System.out.println("默認方法-MyInterface-defaultMethod");}/*** 靜態方法* @return*/static void staticMethod() {// 靜態方法只能調用靜態私有方法,不能調用私有方法System.out.println("靜態方法-MyInterface-staticMethod");}/*** 私有方法*/private void privateMethod() {System.out.println("私有方法-MyInterface-privateMethod");}/*** 靜態私有方法*/private static void staticPrivateMethod() {System.out.println("靜態私有方法-MyInterface-staticPrivateMethod");}
}
接口 MyInterface 里面定義了抽象方法,默認方法,靜態方法,私有方法。這里要注意一下幾點:
- 私有方法為接口所有,只能在接口的默認方法中調用。
- 靜態私有方法既可以在靜態方法中調用,也可以在默認方法中調用。
- 默認方法也可以調用靜態方法,但是靜態方法不能調用默認方法。
- 兩個實現類
public class MyImplements1 implements MyInterface{@Overridepublic void abstractMethod() {// 調用接口的靜態方法MyInterface.staticMethod();System.out.println("抽象方法-MyImplements1-getName");}/*** 重寫默認方法*/@Overridepublic void defaultMethod() {System.out.println("重寫默認方法-MyImplements1-defaultMethod");}
}public class MyImplements2 implements MyInterface{@Overridepublic void abstractMethod() {// 調用接口的靜態方法MyInterface.staticMethod();System.out.println("抽象方法-MyImplements1-getName");}
}
MyImplements1 重寫了默認方法。所以當我們調用 MyImplements1 示例對象的 defaultMethod()
時,會調用到重寫的這個方法。
- 測試
public class InterfaceTest {public static void main(String[] args) {MyInterface implements1 = new MyImplements1();MyInterface implements2 = new MyImplements2();implements1.abstractMethod();implements1.defaultMethod();System.out.println("===========================");implements2.abstractMethod();implements2.defaultMethod();}
}
- 運行結果
靜態方法-MyInterface-staticMethod
抽象方法-MyImplements1-getName
重寫默認方法-MyImplements1-defaultMethod
===========================
靜態方法-MyInterface-staticMethod
抽象方法-MyImplements1-getName
私有方法-MyInterface-privateMethod
靜態私有方法-MyInterface-staticPrivateMethod
靜態方法-MyInterface-staticMethod
默認方法-MyInterface-defaultMethod