介面

Chengkai730發表於2024-09-11

介面的由來

兔子不會游泳,游泳這個方法放在動物類中,顯然是不合適的。而貓和狗都有游泳這個方法,那麼如果貓和狗各自寫自己的游泳方法,則可能導致這個方法不統一。

於是就可以定義一個介面,在其中規定了游泳這個方法的書寫規則,讓貓和狗各自去實現。

而看家這個方法又是狗這個類所特有的,就不要放進介面中。

所以介面放的就是部分類公共的方法,給這些方法規定一個統一的規則。

介面定義的不是繼承的規則,只是一個功能,或者說是一種規則,是對行為的抽象。

介面的定義和形式

介面用關鍵字 interface 來定義:public interface 介面名 {}

介面不能例項化。

介面和類之間是實現關係,透過 implements 關鍵字來表示:public class 類名 implements 介面名 {}

介面的子類也叫實現類。實現類要麼重寫介面中的所有抽象方法,要麼實現類本身也是一個抽象類。一般都是重寫介面中的所有抽象方法。

介面和類之間的實現關係,可以是單實現,也可以是多實現。

多實現:public class 類名 implements 介面名 1, 介面名 2 {}

實現類可以在繼承一個類的同時實現多個介面:public class 類名 extends 父類 implements 介面名 1, 介面名 2 {}

新建介面檔案:

如果新建介面檔案時,忘了選擇 interface 選項,可以在建立完檔案後將 class 改為 interface,檔案圖示也會跟著改過來。

練習:

編寫帶有介面和抽象類的標準 Javabean 類

青蛙 屬性:名字,年齡 行為:吃蟲子,蛙泳

狗 屬性:名字,年齡 行為:吃骨頭,狗刨

兔子 屬性:名字,年齡 行為:吃胡蘿蔔

Javabean 類:

package demo;

public abstract class Animal {
    String name;
    int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // 三個子類都有吃這個方法,而具體的方法體不同,則將吃這個方法抽取到父類中,且定義為抽象類,強制子類重寫
    public abstract void eat();
}

介面:

package demo;

public interface Swim {
    public abstract void swim();
}

Javabean 類:

package demo;

public class Dog extends Animal implements Swim {
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("狗吃骨頭。");
    }

    @Override
    public void swim() {
        System.out.println("狗在狗刨。");
    }
}

Javabean 類:

package demo;

public class Frog extends Animal implements Swim {
    public Frog() {
    }

    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void swim() {
        System.out.println("青蛙在蛙泳。");
    }

    @Override
    public void eat() {
        System.out.println("青蛙在吃蟲子。");
    }
}

Javabean 類:

package demo;

public class Rabbit extends Animal {
    public Rabbit() {
    }

    public Rabbit(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("兔子在吃胡蘿蔔。");
    }
}

測試類:

package demo;

public class Test {
    public static void main(String[] args) {
        Frog f = new Frog("小青", 23);
        System.out.println(f.getName() + ", " + f.getAge());
        f.eat();
        f.swim();

        Rabbit r = new Rabbit("小白", 21);
        System.out.println(r.getName() + ", " + r.getAge());
        r.eat();
        // r.swim();
    }
}

執行結果:

小青, 23
青蛙在吃蟲子。
青蛙在蛙泳。
小白, 21
兔子在吃胡蘿蔔。

介面中成員的特點

成員變數

只能是常量。

預設修飾符:public static final。預設指的是就算不寫,也預設是存在的。介面是一種規則,規則是不能被改變的,所以介面裡的成員變數都是常量,所以用 final 修飾。用 static 來修飾是為了方便呼叫,用 介面名.常量名 就可以呼叫。public 表示公共的,即所有的地方都可以使用介面裡的常量。

構造方法

沒有構造方法。因為介面不能建立物件,並且介面也不需要給子類的成員變數賦值。

成員方法

在 JDK 7 之前,只能寫抽象方法,預設修飾符為 public abstract,即哪怕不寫虛擬機器也會自動幫你加上。

JDK 8:介面中可以定義有方法體的方法。

JDK 9:介面中可以定義私有方法。

程式示例:

介面:

package demo3;

public interface Inter {
    int a = 10;
}

測試類:

package demo3;

public class Test {
    public static void main(String[] args) {
        System.out.println(Inter.a);  // 列印 10,此處是用 介面名.變數名 的形式來呼叫的,由此可以說明是用 static 來修飾的。
        // Inter.a = 20;  // 報錯:cannot assign a value to final variable 'a'  由此說明是用 final 修飾的。
    }
}

介面和類之間的關係:

類和類的關係:繼承關係,只能單繼承,不能多繼承,但是可以多層繼承

類和介面的關係:實現關係,可以單實現,也可以多實現,還可以在繼承一個類的同時實現多個介面. 如果一個類實現了多個介面, 那麼就要重寫全部的抽象方法.

介面和介面的關係:繼承關係,可以單繼承,也可以多繼承。如果實現類實現了最下面的子介面的話,那麼就需要重寫所有的抽象方法。

如果實現多個介面時,多個介面有相同的方法,那麼在子類中只需要重寫一次重複的方法。

介面 1:

public interface Inter1 {
    public abstract void method1();

    public abstract void method_abc();
}

介面 2:

public interface Inter2 {
    public abstract void method2();

    public abstract void method_abc();
}

介面 3:

public interface Inter3 extends Inter2, Inter1 {
    public abstract void method3();
}

實現類:

public class InterImpl implements Inter3{  // 該類實現了最下面的子介面,需要重寫所有的方法
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void method_abc() {  // 重複的方法只需要實現一遍

    }

    @Override
    public void method3() {

    }
}

介面 3 繼承自介面 1 和介面 2,實現類實現了介面 3,則實現類中要重寫三個介面的全部方法。

練習:

編寫帶有介面和抽象類的標準 Javabean 類

我們現在有乒乓球運動員和籃球運動員,乒乓球教練和籃球教練。

為了出國交流,跟乒乓球相關的人員都需要學習英語。

請用所有知識分析,在這個案例中,哪些是具體類,哪些是抽象類,哪些是介面?

乒乓球運動員:姓名,年齡,學打乒乓球,說英語

籃球運動員:姓名,年齡,學打籃球

乒乓球教練:姓名,年齡,教打乒乓球,說英語

籃球教練:姓名,年齡,教打籃球

Javabean 類:

package demo5;

// 因為現在我不想讓外界去直接建立人的物件
// 因為直接建立頂層父類人的物件此時是沒有意義的
// 所以我就把他寫為抽象的。

public abstract class Person {
    private String name;
    private int age;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
package demo5;

public abstract class Sporter extends Person {
    public Sporter() {
    }

    public Sporter(String name, int age) {
        super(name, age);
    }

    public abstract void study();
}
package demo5;

public abstract class Coach extends Person {
    public Coach() {
    }

    public Coach(String name, int age) {
        super(name, age);
    }

    public abstract void teach();
}
package demo5;

public class PingPangSporter extends Sporter implements English {
    public PingPangSporter() {
    }

    public PingPangSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球運動員說英語。");
    }

    @Override
    public void study() {
        System.out.println("乒乓球運動員學習怎麼打乒乓球。");
    }
}
package demo5;

public class BasketballSporter extends Sporter {
    public BasketballSporter() {
    }

    public BasketballSporter(String name, int age) {
        super(name, age);
    }

    @Override
    public void study() {
        System.out.println("籃球運動員在學打籃球。");
    }
}
package demo5;

public class PingPangCoach extends Coach implements English {
    public PingPangCoach() {
    }

    public PingPangCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("乒乓球教練在教別人打乒乓球。");
    }

    @Override
    public void speakEnglish() {
        System.out.println("乒乓球教練在學習英語。");
    }
}
package demo5;

public class BasketballCoach extends Coach {
    public BasketballCoach() {
    }

    public BasketballCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void teach() {
        System.out.println("籃球教練在教別人打籃球。");
    }
}

介面類:

package demo5;

public interface English {
    public abstract void speakEnglish();
}

測試類:

package demo5;

public class Test {
    public static void main(String[] args) {
        PingPangSporter pps = new PingPangSporter("福原愛", 23);
        System.out.println(pps.getName() + ", " + pps.getAge());
        pps.study();
        pps.speakEnglish();
    }
}

執行結果:

福原愛, 23
乒乓球運動員學習怎麼打乒乓球。
乒乓球運動員說英語。

JDK 8 開始介面中新增的方法

JDK 7 之前,介面中只能定義抽象方法。

JDK 8 的新特性:介面中可以定義有方法體的方法,分兩種,一種是預設的方法,一種是靜態的方法。

JDK 9 的新特性:介面中可以定義私有方法,即用 private 修飾。

Java 為什麼這麼設計?

按照 JDK 7 的規定,如果介面變了,那麼所有的實現類都需要進行修改。

介面中有方法體的方法,是在介面升級時,為了相容性而使用的。介面新增了有方法體的方法後,實現類不需要立馬重寫,不重寫也不會報錯,當這個實現類要用到這個方法的時候再去重寫即可。

JDK 8 開始,允許在介面中定義預設方法,用關鍵字 default 修飾。作用就是解決介面升級帶來的相容性問題。

介面中預設方法的定義格式:

public default 返回值型別 方法名 (引數列表) {}

例如:

public default void show () {}

預設方法不是抽象方法,不會被強制重寫,但是如果被重寫,重寫的時候要去掉 default 關鍵字。

public 是預設修飾符,可以省略,但是 default 不能被省略。

如果一個實現類實現了多個介面,多個介面中存在相同名字的預設方法,子類就必須對這個方法進行重寫。

程式示例:

介面:

package demo6;

public interface Inter {

    public abstract void method();

    public default void show() {
        System.out.println("介面的預設方法 ---- show");
    }
}

實現:

package demo6;

public class ImplInter implements Inter{

    @Override
    public void method() {
        System.out.println("實現類的 method 方法。");
    }
}

測試類:

package demo6;

public class Test {
    public static void main(String[] args) {
        ImplInter ii = new ImplInter();
        ii.method();
        ii.show();
    }
}

執行結果:

實現類的 method 方法。
介面的預設方法 ---- show

程式示例:

介面:

package demo6;

public interface Inter {

    public abstract void method();

    public default void show() {
        System.out.println("介面的預設方法 ---- show");
    }
}

實現類:

package demo6;

public class ImplInter implements Inter {

    @Override
    public void method() {
        System.out.println("實現類的 method 方法。");
    }

    @Override
    public void show() {
        System.out.println("重寫介面的預設方法。");
    }
}

測試類:

package demo6;

public class Test {
    public static void main(String[] args) {
        ImplInter ii = new ImplInter();
        ii.method();
        ii.show();
    }
}

執行結果:

實現類的 method 方法。
重寫介面的預設方法。

如果兩個介面中有同名的預設方法, 則實現了這兩個介面的類必須重寫這個方法, 否則類的物件在呼叫這個預設方法時, 不知道是呼叫哪個介面中的預設方法. 程式示例:

第一個介面:

public interface InterA {

    public abstract void methodA();

    public default void show() {
        System.out.println("InterA 裡面的 show()");
    }
}

第二個介面:

public interface InterB {
    public abstract void methodB();

    public default void show() {
        System.out.println("InterB 裡面的 show()");
    }
}

實現類:

public class InterImpl implements InterA,InterB{
    @Override
    public void methodA() {

    }

    @Override
    public void show() {  // 這個 default 方法被強制重寫,因為兩個介面都有這個方法,
        System.out.println("實現類中的重寫的預設方法");
    }

    @Override
    public void methodB() {

    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        InterImpl inter = new InterImpl();
        inter.show();  // 實現類中的重寫的預設方法
    }
}

JDK 8 開始允許在介面中定義靜態方法,用 static 修飾。

介面中的靜態方法的定義格式:

public static 返回值型別 方法名 (引數列表) {}

例如:

public static void show () {}

介面中靜態方法的注意事項:

靜態方法只能透過介面名呼叫,不能透過實現類名或物件名來呼叫。

public 可以省略,但是 static 不能被省略。如果 static 省略不寫, 則會被當為抽象方法, 因為 abstract 是預設的, 不寫就預設是 abstract.

靜態方法不需要重寫.

程式示例:

介面程式碼:

public interface Inter {
    // 介面中的抽象方法
    public abstract void method();

    // 介面中的靜態方法
    public static void show() {
        System.out.println("介面中的靜態方法");
    }
}

實現類的程式碼:

public class InterImpl implements Inter{
    // 重寫介面的抽象方法
    @Override
    public void method() {
        System.out.println("InterImpl 重寫的抽象方法.");
    }

    // 介面的靜態方法不需要重寫

    // 這個不是重寫, 只是實現類裡面有一個和介面裡面同名的靜態方法
    // 如果呼叫時, 用 InterImpl.show(), 則呼叫的是子類裡面的 show() 方法,
    // 用 Inter.show(), 則呼叫的是介面裡面的 show() 方法
    public static void show(){
        System.out.println("InterImpl 的 show() 方法.");
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        // 呼叫介面中的show()方法
        Inter.show();  // 介面中的靜態方法

        // 呼叫實現類中的靜態方法
        InterImpl.show();  // InterImpl 的 show() 方法.
    }
}

私有方法分兩種:普通的和靜態的。

介面中私有方法的定義格式:

普通私有方法,是給預設方法服務的:

格式:private 返回值型別 方法名(引數列表){}

範例:private void show(){}

加了 static 的私有方法,即靜態的私有方法,是給靜態方法服務的:

格式:private static 返回值型別 方法名(引數列表){}

範例:private static void method(){}

程式示例:

介面:

package demo6;

public interface Inter {
    public default void show1() {
        System.out.println("show1方法開始執行。");
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }

    public default void show2() {
        System.out.println("show2方法開始執行。");
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }
}

顯然兩個方法中有重複程式碼,可以進行抽取。

package demo6;

public interface Inter {
    public default void show1() {
        System.out.println("show1方法開始執行。");
        show3();
    }

    public default void show2() {
        System.out.println("show2方法開始執行。");
        show3();
    }

    public default void show3() {
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }
}

抽取出來之後在 show1() 和 show2() 中進行呼叫這個抽取出來的 show3() 即可。

但是這個方法不希望被外面的其他類呼叫,因為毫無意義。因此規定其為 private 並刪掉 default。

package demo6;

public interface Inter {
    public default void show1() {
        System.out.println("show1方法開始執行。");
        show3();
    }

    public default void show2() {
        System.out.println("show2方法開始執行。");
        show3();
    }

    private void show3() {  // 這就是普通的私有方法, 即沒有用 static 修飾的私有方法
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }
}

針對靜態方法則需要靜態私有方法為其服務:

package demo6;

public interface Inter {
    public static void show1() {
        System.out.println("show1方法開始執行。");
        show4();
    }

    public static void show2() {
        System.out.println("show2方法開始執行。");
        show4();
    }

    private void show3() {
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }

    private static void show4() {  // 加 static 修飾的靜態私有方法
        System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
    }
}

當一個方法的形參是介面時, 可以傳遞介面所有實現類的物件, 這種方式稱為介面多型.

相關文章