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

讀者春夏秋冬在抽象類的那篇文章中留言涩澡,“二哥顽耳,面試官最喜歡問的一個問題就是,‘兄弟妙同,說說抽象類和接口之間的區(qū)別射富?’,啥時候講講接口唄粥帚!”

對于面向?qū)ο缶幊虂碚f胰耗,抽象是一個極具魅力的特征。如果一個程序員的抽象思維很差茎辐,那他在編程中就會遇到很多困難宪郊,無法把業(yè)務(wù)變成具體的代碼。在 Java 中拖陆,可以通過兩種形式來達到抽象的目的弛槐,一種是抽象類,另外一種就是接口依啰。

如果你現(xiàn)在就想知道抽象類與接口之間的區(qū)別乎串,我可以提前給你說一個:

  • 一個類只能繼承一個抽象類,但卻可以實現(xiàn)多個接口速警。

當然了叹誉,在沒有搞清楚接口到底是什么,它可以做什么之前闷旧,這個區(qū)別理解起來會有點難度长豁。

01、接口是什么

接口是通過 interface 關(guān)鍵字定義的忙灼,它可以包含一些常量和方法匠襟,來看下面這個示例钝侠。

public interface Electronic {
    // 常量
    String LED = "LED";

    // 抽象方法
    int getElectricityUse();

    // 靜態(tài)方法
    static boolean isEnergyEfficient(String electtronicType) {
        return electtronicType.equals(LED);
    }

    // 默認方法
    default void printDescription() {
        System.out.println("電子");
    }
}

1)接口中定義的變量會在編譯的時候自動加上 public static final 修飾符,也就是說 LED 變量其實是一個常量酸舍。

Java 官方文檔上有這樣的聲明:

Every field declaration in the body of an interface is implicitly public, static, and final.

換句話說帅韧,接口可以用來作為常量類使用,還能省略掉 public static final啃勉,看似不錯的一種選擇忽舟,對吧?

不過淮阐,這種選擇并不可取叮阅。因為接口的本意是對方法進行抽象,而常量接口會對子類中的變量造成命名空間上的“污染”泣特。

2)沒有使用 private帘饶、default 或者 static 關(guān)鍵字修飾的方法是隱式抽象的,在編譯的時候會自動加上 public abstract 修飾符群扶。也就是說 getElectricityUse() 其實是一個抽象方法,沒有方法體——這是定義接口的本意镀裤。

3)從 Java 8 開始竞阐,接口中允許有靜態(tài)方法,比如說 isEnergyEfficient() 方法暑劝。

靜態(tài)方法無法由(實現(xiàn)了該接口的)類的對象調(diào)用骆莹,它只能通過接口的名字來調(diào)用,比如說 Electronic.isEnergyEfficient("LED")担猛。

接口中定義靜態(tài)方法的目的是為了提供一種簡單的機制幕垦,使我們不必創(chuàng)建對象就能調(diào)用方法,從而提高接口的競爭力傅联。

4)接口中允許定義 default 方法也是從 Java 8 開始的先改,比如說 printDescription(),它始終由一個代碼塊組成蒸走,為實現(xiàn)該接口而不覆蓋該方法的類提供默認實現(xiàn)仇奶,也就是說,無法直接使用一個“;”號來結(jié)束默認方法——編譯器會報錯的比驻。

允許在接口中定義默認方法的理由是很充分的该溯,因為一個接口可能有多個實現(xiàn)類,這些類就必須實現(xiàn)接口中定義的抽象類别惦,否則編譯器就會報錯狈茉。假如我們需要在所有的實現(xiàn)類中追加某個具體的方法,在沒有 default 方法的幫助下掸掸,我們就必須挨個對實現(xiàn)類進行修改氯庆。

來看一下 Electronic 接口反編譯后的字節(jié)碼吧,你會發(fā)現(xiàn),接口中定義的所有變量或者方法点晴,都會自動添加上 public 關(guān)鍵字——假如你想知道編譯器在背后都默默做了哪些輔助感凤,記住反編譯字節(jié)碼就對了。

public interface Electronic
{

    public abstract int getElectricityUse();

    public static boolean isEnergyEfficient(String electtronicType)
    {
        return electtronicType.equals("LED");
    }

    public void printDescription()
    {
        System.out.println("\u7535\u5B50");
    }

    public static final String LED = "LED";
}

有些讀者可能會問粒督,“二哥陪竿,為什么我反編譯后的字節(jié)碼和你的不一樣,你用了什么反編譯工具屠橄?”其實沒有什么秘密族跛,微信搜「沉默王二」回復(fù)關(guān)鍵字「JAD」就可以免費獲取了,超級好用锐墙。

02礁哄、定義接口的注意事項

由之前的例子我們就可以得出下面這些結(jié)論:

  • 接口中允許定義變量
  • 接口中允許定義抽象方法
  • 接口中允許定義靜態(tài)方法(Java 8 之后)
  • 接口中允許定義默認方法(Java 8 之后)

除此之外,我們還應(yīng)該知道:

1)接口不允許直接實例化溪北。

需要定義一個類去實現(xiàn)接口桐绒,然后再實例化百宇。

public class Computer implements Electronic {

    public static void main(String[] args) {
        new Computer();
    }

    @Override
    public int getElectricityUse() {
        return 0;
    }
}

2)接口可以是空的濒持,既不定義變量,也不定義方法纹烹。

public interface Serializable {
}

Serializable 是最典型的一個空的接口蚀乔,我之前分享過一篇文章《Java Serializable:明明就一個空的接口嘛》烁竭,感興趣的讀者可以去我的個人博客看一看,你就明白了空接口的意義吉挣。

http://www.itwanger.com/java/2019/11/14/java-serializable.html

3)不要在定義接口的時候使用 final 關(guān)鍵字派撕,否則會報編譯錯誤,因為接口就是為了讓子類實現(xiàn)的睬魂,而 final 阻止了這種行為终吼。

4)接口的抽象方法不能是 private、protected 或者 final汉买。

5)接口的變量是隱式 public static final衔峰,所以其值無法改變。

03蛙粘、接口可以做什么

1)使某些實現(xiàn)類具有我們想要的功能垫卤,比如說,實現(xiàn)了 Cloneable 接口的類具有拷貝的功能出牧,實現(xiàn)了 Comparable 或者 Comparator 的類具有比較功能穴肘。

Cloneable 和 Serializable 一樣,都屬于標記型接口舔痕,它們內(nèi)部都是空的评抚。實現(xiàn)了 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();
    }
}

運行后沒有報錯】現(xiàn)在把 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 原則上只支持單一繼承,但通過接口可以實現(xiàn)多重繼承的目的想暗。

可能有些讀者會問妇汗,“二哥,為什么 Java 只支持單一繼承说莫?”簡單來解釋一下杨箭。

如果有兩個類共同繼承(extends)一個有特定方法的父類,那么該方法會被兩個子類重寫储狭。然后互婿,如果你決定同時繼承這兩個子類,那么在你調(diào)用該重寫方法時辽狈,編譯器不能識別你要調(diào)用哪個子類的方法擒悬。這也正是著名的菱形問題,見下圖稻艰。

ClassC 同時繼承了 ClassA 和 ClassB,ClassC 的對象在調(diào)用 ClassA 和 ClassB 中重載的方法時侈净,就不知道該調(diào)用 ClassA 的方法尊勿,還是 ClassB 的方法。

接口沒有這方面的困擾畜侦。來定義兩個接口元扔,F(xiàn)ly 會飛,Run 會跑旋膳。

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

然后讓一個類同時實現(xiàn)這兩個接口澎语。

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

    @Override
    public void run() {
        System.out.println("會跑的豬");
    }
}

這就在某種形式上達到了多重繼承的目的:現(xiàn)實世界里,豬的確只會跑验懊,但在雷軍的眼里擅羞,站在風口的豬就會飛,這就需要賦予這只豬更多的能力义图,通過抽象類是無法實現(xiàn)的减俏,只能通過接口。

3)實現(xiàn)多態(tài)碱工。

什么是多態(tài)呢娃承?通俗的理解奏夫,就是同一個事件發(fā)生在不同的對象上會產(chǎn)生不同的結(jié)果,鼠標左鍵點擊窗口上的 X 號可以關(guān)閉窗口历筝,點擊超鏈接卻可以打開新的網(wǎng)頁酗昼。

多態(tài)可以通過繼承(extends)的關(guān)系實現(xiàn),也可以通過接口的形式實現(xiàn)梳猪。來看這樣一個例子麻削。

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());
}

多態(tài)的存在 3 個前提:

1惕稻、要有繼承關(guān)系竖共,Circle 和 Square 都實現(xiàn)了 Shape 接口
2、子類要重寫父類的方法俺祠,Circle 和 Square 都重寫了 name() 方法
3公给、父類引用指向子類對象,circleShape 和 squareShape 的類型都為 Shape蜘渣,但前者指向的是 Circle 對象淌铐,后者指向的是 Square 對象。

然后蔫缸,我們來看一下測試結(jié)果:

圓
正方形

也就意味著腿准,盡管在 for 循環(huán)中,shape 的類型都為 Shape拾碌,但在調(diào)用 name() 方法的時候吐葱,它知道 Circle 對象應(yīng)該調(diào)用 Circle 類的 name() 方法,Square 對象應(yīng)該調(diào)用 Square 類的 name() 方法校翔。

04弟跑、接口與抽象類的區(qū)別

好了,關(guān)于接口的一切防症,你應(yīng)該都搞清楚了∶霞現(xiàn)在回到讀者春夏秋冬的那條留言,“兄弟蔫敲,說說抽象類和接口之間的區(qū)別饲嗽?”

1)語法層面上

  • 接口中不能有 public 和 protected 修飾的方法,抽象類中可以有奈嘿。
  • 接口中的變量只能是隱式的常量喝噪,抽象類中可以有任意類型的變量。
  • 一個類只能繼承一個抽象類指么,但卻可以實現(xiàn)多個接口酝惧。

2)設(shè)計層面上

抽象類是對類的一種抽象榴鼎,繼承抽象類的類和抽象類本身是一種 is-a 的關(guān)系。

接口是對類的某種行為的一種抽象晚唇,接口和類之間并沒有很強的關(guān)聯(lián)關(guān)系巫财,所有的類都可以實現(xiàn) Serializable 接口,從而具有序列化的功能哩陕。

就這么多吧平项,能說道這份上,我相信面試官就不會為難你了悍及。

如果覺得文章對你有點幫助闽瓢,請微信搜索「 沉默王二 」第一時間閱讀,回復(fù)「并發(fā)」更有一份阿里大牛重寫的 Java 并發(fā)編程實戰(zhàn)心赶,從此再也不用擔心面試官在這方面的刁難了扣讼。

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

我是沉默王二耻姥,一枚有顏值卻靠才華茍且的程序員销钝。關(guān)注即可提升學(xué)習(xí)效率,別忘了三連啊琐簇,點贊蒸健、收藏、留言婉商,我不挑纵装,嘻嘻

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末据某,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子诗箍,更是在濱河造成了極大的恐慌癣籽,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滤祖,死亡現(xiàn)場離奇詭異筷狼,居然都是意外死亡,警方通過查閱死者的電腦和手機匠童,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門埂材,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汤求,你說我怎么就攤上這事俏险⊙暇埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵竖独,是天一觀的道長裤唠。 經(jīng)常有香客問我,道長莹痢,這世上最難降的妖魔是什么种蘸? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮竞膳,結(jié)果婚禮上航瞭,老公的妹妹穿的比我還像新娘。我一直安慰自己坦辟,他們只是感情好刊侯,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著长窄,像睡著了一般滔吠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挠日,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天疮绷,我揣著相機與錄音,去河邊找鬼嚣潜。 笑死冬骚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的懂算。 我是一名探鬼主播只冻,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼计技!你這毒婦竟也來了喜德?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤垮媒,失蹤者是張志新(化名)和其女友劉穎舍悯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睡雇,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡萌衬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了它抱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秕豫。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖观蓄,靈堂內(nèi)的尸體忽然破棺而出混移,到底是詐尸還是另有隱情祠墅,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布沫屡,位于F島的核電站饵隙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沮脖。R本人自食惡果不足惜金矛,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勺届。 院中可真熱鬧驶俊,春花似錦、人聲如沸免姿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胚膊。三九已至故俐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間紊婉,已是汗流浹背药版。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喻犁,地道東北人槽片。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像肢础,于是被迫代替她去往敵國和親还栓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355