天天看點

可能是把 Java 接口講得最通俗的一篇文章(2)

03、接口可以做什麼

1)使某些實作類具有我們想要的功能,比如說,實作了 Cloneable 接口的類具有拷貝的功能,實作了 Comparable 或者 Comparator 的類具有比較功能。

Cloneable 和 Serializable 一樣,都屬于标記型接口,它們内部都是空的。實作了 Cloneable 接口的類可以使用 Object.clone() 方法,否則會抛出 CloneNotSupportedException。

public class CloneableTest implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();
    }
}      

運作後沒有報錯。現在把 implements Cloneable 去掉。

public class CloneableTest {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();
    }
}      

運作後抛出 CloneNotSupportedException:

Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
    at java.base/java.lang.Object.clone(Native Method)
    at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
    at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)      

至于 Comparable 和 Comparator 的用法,感興趣的讀者可以參照我之前寫的另外一篇文章《來吧,一文徹底搞懂Java中的Comparable和Comparator》。

http://www.itwanger.com/java/2020/01/04/java-comparable-comparator.html

2)Java 原則上隻支援單一繼承,但通過接口可以實作多重繼承的目的。

可能有些讀者會問,“二哥,為什麼 Java 隻支援單一繼承?”簡單來解釋一下。

如果有兩個類共同繼承(extends)一個有特定方法的父類,那麼該方法會被兩個子類重寫。然後,如果你決定同時繼承這兩個子類,那麼在你調用該重寫方法時,編譯器不能識别你要調用哪個子類的方法。這也正是著名的菱形問題,見下圖。

可能是把 Java 接口講得最通俗的一篇文章(2)

ClassC 同時繼承了 ClassA 和 ClassB,ClassC 的對象在調用 ClassA 和 ClassB 中重載的方法時,就不知道該調用 ClassA 的方法,還是 ClassB 的方法。

接口沒有這方面的困擾。來定義兩個接口,Fly 會飛,Run 會跑。

public interface Fly {
    void fly();
}
public interface Run {
    void run();
}      

然後讓一個類同時實作這兩個接口。

public class Pig implements Fly,Run{
    @Override
    public void fly() {
        System.out.println("會飛的豬");
    }
    @Override
    public void run() {
        System.out.println("會跑的豬");
    }
}      

這就在某種形式上達到了多重繼承的目的:現實世界裡,豬的确隻會跑,但在雷軍的眼裡,站在風口的豬就會飛,這就需要賦予這隻豬更多的能力,通過抽象類是無法實作的,隻能通過接口。

3)實作多态。

什麼是多态呢?通俗的了解,就是同一個事件發生在不同的對象上會産生不同的結果,滑鼠左鍵點選視窗上的 X 号可以關閉視窗,點選超連結卻可以打開新的網頁。

多态可以通過繼承(extends)的關系實作,也可以通過接口的形式實作。來看這樣一個例子。

Shape 是表示一個形狀。
public interface Shape {
    String name();
}
圓是一個形狀。
public class Circle implements Shape {
    @Override
    public String name() {
        return "圓";
    }
}
正方形也是一個形狀。
public class Square implements Shape {
    @Override
    public String name() {
        return "正方形";
    }
}      

然後來看測試類。

List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();
shapes.add(circleShape);
shapes.add(squareShape);
for (Shape shape : shapes) {
    System.out.println(shape.name());
}      

多态的存在 3 個前提:

1、要有繼承關系,Circle 和 Square 都實作了 Shape 接口

2、子類要重寫父類的方法,Circle 和 Square 都重寫了 name() 方法

3、父類引用指向子類對象,circleShape 和 squareShape 的類型都為 Shape,但前者指向的是 Circle 對象,後者指向的是 Square 對象。

然後,我們來看一下測試結果:

正方形

1

2

也就意味着,盡管在 for 循環中,shape 的類型都為 Shape,但在調用 name() 方法的時候,它知道 Circle 對象應該調用 Circle 類的 name() 方法,Square 對象應該調用 Square 類的 name() 方法。

04、接口與抽象類的差別

好了,關于接口的一切,你應該都搞清楚了。現在回到讀者春夏秋冬的那條留言,“兄弟,說說抽象類和接口之間的差別?”

1)文法層面上

接口中不能有 public 和 protected 修飾的方法,抽象類中可以有。

接口中的變量隻能是隐式的常量,抽象類中可以有任意類型的變量。

一個類隻能繼承一個抽象類,但卻可以實作多個接口。

2)設計層面上

抽象類是對類的一種抽象,繼承抽象類的類和抽象類本身是一種 is-a 的關系。

接口是對類的某種行為的一種抽象,接口和類之間并沒有很強的關聯關系,所有的類都可以實作 Serializable 接口,進而具有序列化的功能。

就這麼多吧,能說道這份上,我相信面試官就不會為難你了。

如果覺得文章對你有點幫助,請微信搜尋「 沉默王二 」第一時間閱讀,回複「并發」更有一份阿裡大牛重寫的 Java 并發程式設計實戰,從此再也不用擔心面試官在這方面的刁難了。

本文已收錄 GitHub,傳送門~ ,裡面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顔值卻靠才華苟且的程式員。關注即可提升學習效率,别忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻。