什麼?接口中方法能夠不是抽象的「JDK8接口新語法的深度思考」

先贊後看,養成習慣java

文本已收錄至GitHub開源倉庫 Lu_JavaNodes 碼雲倉庫地址Lu_JavaNodes,包含教程涉及全部思惟導圖,案例代碼和後續講解視頻,歡迎Star增磚添瓦。git

前言

在傳統的接口語法中,接口中只能夠有抽象方法。在是在實際的使用中,咱們每每會須要用到不少和接口相關的功能(方法),這些功能會單獨的拿出開放在工具類中。github

工具類:類中全部的方法都是靜態的web

例如:Collection 和 Collocations,Collection 是一個集合接口,而咱們須要不少集合相關的操做,像集合的排序,搜索等等, 這時候人們會把這些靜態方法放在 Collections 工具類中。 面試

在傳統Java中咱們常常會看到這樣的狀況,有一個接口叫 A,這時候就會有一個類叫 As,As中全是和A接口有關的靜態方法。
例如:Executor 和 Executorsapp

這樣的一種方式總歸來講是有點不方便。因而在JDK8中Java對於接口作了一些改動,容許將靜態方法直接寫入接口中。(接口中能夠定義靜態方法,靜態方法確定不是抽象的,是有實現的)。ide

接口的靜態方法

代碼案例

根據上述內容,咱們來定義一個接口,在接口中寫入一個靜態方法。工具

public class TestStaticInterface {

    public static void main(String[] args{
//        靜態方法能夠經過類名直接調用  接口能夠說是特殊的類 因此經過接口名能夠調用接口中的靜態方法
        HelloInterface.printHello();
    }

}

interface HelloInterface{
    int hhh();

//    定義靜態方法
    static void printHello(){
        System.out.println("Hello");
    }
}
複製代碼

運行代碼能夠看到以下結果
學習

靜態方法有什麼用呢?

靜態方法其實是很實用的,最基本的用法:咱們能夠把產生接口對象的方法放在接口中。ui

什麼意思???好,接下來咱們經過代碼演示一下。

假設如今咱們有一個 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實戰開發經驗。

相關文章
相關標籤/搜索