先贊後看,養成習慣
文本已收錄至GitHub開源倉庫 Lu_JavaNodes 碼雲倉庫地址Lu_JavaNodes,包含教程涉及所有思維導圖,案例代碼和後續講解視頻,歡迎Star增磚添瓦。
前言
在傳統的接口語法中,接口中只可以有抽象方法。在是在實際的使用中,我們往往會需要用到很多和接口相關的功能(方法),這些功能會單獨的拿出開放在工具類中。
工具類:類中所有的方法都是靜態的
例如:Collection 和 Collocations,Collection 是一個集合接口,而我們需要很多集合相關的操作,像集合的排序,搜索等等, 這時候人們會把這些靜態方法放在 Collections 工具類中。
在傳統Java中我們經常會看到這樣的情況,有一個接口叫 A,這時候就會有一個類叫 As,As中全是和A接口有關的靜態方法。
例如:Executor 和 Executors
這樣的一種方式總歸來說是有點不方便。於是在JDK8中Java對於接口做了一些改動,允許將靜態方法直接寫入接口中。(接口中可以定義靜態方法,靜態方法肯定不是抽象的,是有實現的)。
接口的靜態方法
代碼案例
根據上述內容,我們來定義一個接口,在接口中寫入一個靜態方法。
public class TestStaticInterface {
public static void main(String[] args) {
// 靜態方法可以通過類名直接調用 接口可以說是特殊的類 所以通過接口名可以調用接口中的靜態方法
HelloInterface.printHello();
}
}
interface HelloInterface{
int hhh();
// 定義靜態方法
static void printHello(){
System.out.println("Hello");
}
}
運行代碼可以看到如下結果
靜態方法有什麼用呢?
靜態方法實際上是很實用的,最基本的用法:我們可以把產生接口對象的方法放在接口中。
什麼意思???好,接下來我們通過代碼演示一下。
假設現在我們有一個 Animal 接口,那麼這時候如果要獲得一個Animal類型的對象,我們要怎麼做呢?
傳統方法,創建一個Animals工具類,在其中有一個 static Animal createDog()
可以獲取一個Animal類型的對象,代碼如下
public class TestStaticInterface {
public static void main(String[] args) {
// 通過工具類獲取對象
Animal animal = Animals.createDog();
}
}
class Animals{
// 靜態方法獲取對象
static Animal createDog(){
// 局部內部類
class Dog implements Animal{
}
// 返回對象
return new Dog();
}
}
但是當你擁抱JDK8的時候,一切都不一樣了,因爲有接口靜態方法,可以直接將接口對象的獲取放在接口的靜態方法中。代碼如下
public class TestStaticInterface {
public static void main(String[] args) {
// 通過接口的靜態方法獲取一個Animal類型的對象
Animal animal = Animal.createDog();
}
}
interface Animal{
// 靜態方法獲取對象
static Animal createDog(){
// 局部內部類
class Dog implements Animal{
}
// 返回對象
return new Dog();
}
}
在JDK 的 API 中是怎麼使用靜態方法的
接下來我們通過Java中的API來驗證一下這種使用方法。通過API文檔,可以找到 Comparator 接口(比較器),在這個接口中現在就有很多的靜態方法(JDK8)。如圖
通過這些靜態方法,就可以通過接口直接獲取比較器對象。
public class TestStaticInterface {
public static void main(String[] args) {
// 通過Comparator接口獲取一個自然排序的比較器(自然排序就是String中默認實現的排序邏輯)
Comparator<String> comparator = Comparator.naturalOrder();
// 創建集合
List<String> list = Arrays.asList("b","a","c");
// 通過比較器對集合進行排序
list.sort(comparator);
for (String s : list) {
System.out.println(s);
}
}
}
傳統接口的另一個問題:向後兼容性不好
現在接口已經有了靜態方法,但是傳統的接口還有另一個問題。我們舉例說明:
假設你正在公司中做項目,在你的代碼中,有一個UserService的接口,接口中有一個方法String getUsernameById()
。
interface UserService{
String getUsernameById();
}
該接口因爲在項目中存在老長時間了,所以實現類衆多,有100個實現類。
one day,領導希望你給這個接口中添加一個新的接口方法String getIdByUsername()
。這樣的需求意味着要修改100個實現類,不要說寫代碼了,刪庫跑路的心都有了。
這是一個極端的案例,但是說明了一個事兒,傳統的接口向後兼容性不好,不易於維護和改造。
而這個問題,在JDK8中得到了解決,解決方法就是:接口的默認方法。
接口的默認方法
Java 8 中允許接口中包含具有具體實現的方法,該方法稱爲 “默認方法”,默認方法使用 default 關鍵字修飾。
在接口中使用 default 表示這個方法有實現,接口中所有的方法都是 public
示例代碼
interface UserService{
String getUsernameById();
// 默認方法
default void m1(){
System.out.println("這是一個默認方法");
}
}
class UserServiceImpl implements UserService{
@Override
public String getUsernameById() {
return null;
}
}
示例代碼的問題
看了這樣的一段代碼,你一定會有一些疑問,我們一起來解決一下。
接口中的默認方法,實現類能不能繼承到?
答:這個當然是可以的,並且在實現類中依然可以進行方法的覆蓋。
如果 UserServiceImpl 再有一個父類,父類中也有m1方法,那麼UserServiceImpl 繼承到的是父類還是接口中的m1方法?
interface UserService{ String getUsernameById(); // 默認方法 default void m1(){ System.out.println("這是一個默認方法"); } } //父類 class UserSer{ public void m1(){ System.out.println("這是一個默認方法"); } } class UserServiceImpl extends UserSer implements UserService{ @Override public String getUsernameById() { return null; } }
答:在實現類中繼承到的是父類中的。因爲接口默認方法有”類優先”的原則。
接口默認方法的”類優先”原則
若一個接口中定義了一個默認方法,而另外一個父類或接口中 又定義了一個同名的方法時
- 選擇父類中的方法。如果一個父類提供了具體的實現,那麼
接口中具有相同名稱和參數的默認方法會被忽略。- 接口衝突。如果一個父接口提供一個默認方法,而另一個接 口也提供了一個具有相同名稱和參數列表的方法(不管方法是否是默認方法),那麼必須覆蓋該方法來解決衝突
對於 JDK8 接口新語法的思考
關於接口新語法的講解實際上已經結束了,但是想要和大家一起延伸一下思考,看下面一個案例。
interface IA{
default void m2(){
System.out.println("IA");
}
}
interface IB{
default void m2(){
System.out.println("IB");
}
}
class ImplC implements IA,IB{
// 接口衝突 通過覆蓋解決
public void m2(){
System.out.println("Impl");
}
}
以上代碼實際上就是 “類優先”原則第二條接口衝突的演示代碼,而我要思考的問題不是這個,而是:1.在實現類中,如何使用super,2.如果IA 和 IB 接口中的m2方法返回值不同怎麼辦?
1.在實現類中,如何使用super?
第一個問題,比較好解決,因爲有m2來自兩個接口,所以我們如果要調用super的話,需要說明要調用那個接口的super,語法:接口名.super.m2()
實現類繼承的方法來自兩個接口,必須覆蓋,否則引用不明確。要調用super,也必須指明要調用那個接口。
其實這個問題來自多繼承,過去接口比較簡單,調用 super肯定不會調用接口,接口中方法都是抽象的,現在不一樣了,父類和接口中都有方法實現,這時候再要調用就要指明要調用誰了。
雖然Java一直都是單繼承,但是這個語法實際上已經是向多繼承靠近了。只不過並沒有把多繼承正式的引入Java,所以會有一定的不足,這就是我們的第二個思考題。
2.如果IA 和 IB 接口中的m2方法返回值不同怎麼辦?
這其實也是一個標準的多繼承的問題,在現版本沒有解決。
在C++中其實就簡單了,可以指定要覆蓋誰
總結
學過了接口的靜態方法和默認方法,彷彿發現了一個事兒,接口和抽象類越來越像了,那麼這時候再問你那個問題:接口和抽象類有什麼區別?
這個問題留給大家,好像以前背答案開始不好使了。
最後我們簡單總結一下JDK8接口語法的新變化:在JDK8以後的接口中,允許有靜態方法和默認方法(default)修飾
求關注,求點贊,求轉發
歡迎關注本人公衆號:鹿老師的Java筆記,將在長期更新Java技術圖文教程和視頻教程,Java學習經驗,Java面試經驗以及Java實戰開發經驗。