1. 程式人生 > >什麼?介面中方法可以不是抽象的「JDK8介面新語法的深度思考」

什麼?介面中方法可以不是抽象的「JDK8介面新語法的深度思考」

先贊後看,養成習慣

文字已收錄至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實戰開發經