從JVM看Java語言特性(四) 接口和抽象類

從JVM看Java語言特性(四) 接口和抽象類

接口和抽象類都是上層抽象, 一個類可以實現(xiàn)多個接口卻只能繼承一個抽象類. 從上一篇文章中我們大致明白了繼承和多態(tài)是如何實現(xiàn)的, 多態(tài)通過JVM在vtable放置不同的方法指針來決定到底是調(diào)用父類的方法還是子類的方法. 按照多態(tài)的這個思路的話, 接口應該就是一個指示器, 方便JVM來判斷vtable里面需要哪些方法. 那么到底是不是這么回事兒我們目前還不知道, 這篇文章讓我們探索一下接口和抽象類的一些細節(jié).

1. 接口與抽象類的實現(xiàn)

如果一個接口里面有一個getA()方法的話, 它的方法字節(jié)碼是這樣的:

 public abstract int getA();
    descriptor: ()I
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT

另一方面, 如果一個抽象類里面只有一個abstract getA()方法, 它的方法字節(jié)碼是這樣的:

public Dao.FInterface();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  abstract int getA();
    descriptor: ()I
    flags: (0x0400) ACC_ABSTRACT

可以看到, 抽象類有構(gòu)造器, 如果里面沒有抽象方法的話它和一般的沒什么區(qū)別. 但是如果抽象類里有抽象方法的話, 那么這些抽象方法和接口里的抽象方法也幾乎沒有區(qū)別, 唯一的區(qū)別是抽象類的抽象方法可以是加上訪問修飾符, 如public或protected, 也可以不加修飾符(給抽象方法加private會報錯, 因為這樣的話這個抽象方法永遠無法被實現(xiàn), 進而就沒辦法生成這個類的對象, 沒有意義), 而接口只能為public(不加修飾符也會默認為public).

這樣來看, 抽象類的結(jié)構(gòu)像是一個普通類加上接口的抽象方法.

接口不能有構(gòu)造器, 因為接口是一種規(guī)范, 類可以實現(xiàn)多個接口,若多個接口都有自己的構(gòu)造器,則不好決定構(gòu)造器鏈的調(diào)用次序.

2. 接口和抽象類的意義

傳統(tǒng)多繼承的復雜度一直是逃避不開的問題, 如果繼承的兩個父類里面包含了相同名稱的方法或參量, 那么 JVM無法判斷到底需要執(zhí)行哪一種, 沖突問題就會非常明顯. 由此, Java采用了單繼承來避免這個問題, 為了提高靈活程度, 又引入接口來實現(xiàn)輕耦合的多繼承. 接口用不會引起沖突的方式解決了多重繼承的問題

接口的目的是實現(xiàn), 它和實現(xiàn)類的關(guān)系是"has a", 也就是說接口可以是實現(xiàn)類的一個組件, 一個部分.

抽象類的目的則是繼承, 它和實現(xiàn)類的關(guān)系是"is a", 其實完全可以不存在抽象類這個東西, 用一個普通類代替抽象類, 將所有抽象方法都改成空的實方法, 然后再讓子類將其重寫, 這也就成了變相的抽象類. 這樣子想下去抽象類的唯一作用就是幫助程序員少犯錯(抽象類無法實現(xiàn), 只能多態(tài), 減少程序員誤操作的可能), 并且?guī)椭绦騿T理順繼承關(guān)系(名字就叫抽象類, 一看就知道可能有多個類繼承于它).

在知乎上看到的一句話說的特別好[傳送門] "抽象類主要是用來抽象類別, 接口主要是用來抽象方法功能. 當你關(guān)注事物的本質(zhì)的時候, 用抽象類; 當你關(guān)注一種操作的時候用接口",

3. 抽象類與接口中的參數(shù)

抽象類由于是一個類, 并且有構(gòu)造函數(shù), 所以它和一個普通類對參數(shù)的處理是一樣的, 具體可見我的這篇文章, 因此這里就不展開了.

但是對于接口來說, 沒有構(gòu)造器的話接口是如何初始化它的數(shù)據(jù)的? 這個問題值得探討. 我們假設(shè)有下面這個接口:

public interface FInterface {

    int a = 1;

    public static int c = 2;

    public static final int d = 1;

    int getA();

}

它的方法字節(jié)碼是:

public static final int a;
    descriptor: I
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public static final int b;
    descriptor: I
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2

  public static final int c;
    descriptor: I
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public abstract int getA();
    descriptor: ()I
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT

經(jīng)過測試, 發(fā)現(xiàn)無論有沒有public static final, 生成的class文件里都會有這三個關(guān)鍵詞. 也就意味著接口里面所有的參數(shù)都是常量, 在類加載過程中都已經(jīng)被解析并且被放進了常量池里.

4. jdk8 和 jdk9 中接口的變化

  1. jdk8 中接口允許有default方法

    default方法是指接口默認實現(xiàn)的方法, 主要是為了解決舊版本的兼容性問題, 以下是無default關(guān)鍵字和有default關(guān)鍵字的字節(jié)碼對比:

     public abstract int getA();
        descriptor: ()I
        flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    
    public int getA();
        descriptor: ()I
        flags: (0x0001) ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: iconst_1
             1: ireturn
          LineNumberTable:
            line 13: 0
    
    

    可以發(fā)現(xiàn)default方法就是一個普通類里面實際存在的方法. 這樣Java就實現(xiàn)了方法的多繼承, 雖然設(shè)計的初衷并不是讓使用者去濫用default, 只是一種為了不破壞之前代碼的一種妥協(xié).

    那如果兩個接口都有相同的方法不就沖突了嗎? 其實可以使用接口名+.super.+方法名來區(qū)分不同的方法, 就像下面這樣:

    A.super.getA();
    B.super.getA();
    

    這樣就不會引起沖突了. 但是Java為了讓程序員少犯錯誤, 除非實現(xiàn)類重寫了這個方法, 不允許實現(xiàn)的兩個接口里有相同的default方法.

    除此之外, jdk9支持接口存在私有方法, 由于私有方法不存在子類直接調(diào)用, 因此也不會有沖突的問題, 這里就不再展開了.

    ?

總結(jié)

? 為了寫這一章, 本人查閱了大量資料, 包括了很多博客. 但是經(jīng)過實踐之后, 很多博客都有或多或少的錯誤, 比如有的說接口不能有靜態(tài)方法(靜態(tài)方法的解析是類加載的一部分, 當然可以有!), 我自己以后寫博客的時候會盡量找官方或者權(quán)威的說法來論證, 不會輕易相信網(wǎng)上的東西.

? 總的來說, 接口和抽象類是越來越相似了, 兩者的主要區(qū)別在于接口可以實現(xiàn)多個和接口沒有構(gòu)造器.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末片排,一起剝皮案震驚了整個濱河市奏纪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熊赖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凹蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門玛痊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汰瘫,“玉大人,你說我怎么就攤上這事卿啡∫髁撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵颈娜,是天一觀的道長剑逃。 經(jīng)常有香客問我,道長官辽,這世上最難降的妖魔是什么蛹磺? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮同仆,結(jié)果婚禮上萤捆,老公的妹妹穿的比我還像新娘。我一直安慰自己俗批,他們只是感情好俗或,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岁忘,像睡著了一般辛慰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上干像,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天帅腌,我揣著相機與錄音,去河邊找鬼麻汰。 笑死速客,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的五鲫。 我是一名探鬼主播溺职,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼臣镣!你這毒婦竟也來了辅愿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤忆某,失蹤者是張志新(化名)和其女友劉穎点待,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃舒,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡癞埠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年状原,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苗踪。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡颠区,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出通铲,到底是詐尸還是另有隱情毕莱,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布颅夺,位于F島的核電站朋截,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吧黄。R本人自食惡果不足惜部服,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拗慨。 院中可真熱鬧廓八,春花似錦、人聲如沸赵抢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烦却。三九已至国夜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間短绸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工筹裕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留醋闭,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓朝卒,卻偏偏與公主長得像证逻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抗斤,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1.import static是Java 5增加的功能,就是將Import類中的靜態(tài)方法囚企,可以作為本類的靜態(tài)方法來...
    XLsn0w閱讀 1,222評論 0 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法瑞眼,內(nèi)部類的語法龙宏,繼承相關(guān)的語法,異常的語法伤疙,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 本文出自 Eddy Wiki 银酗,轉(zhuǎn)載請注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 1,201評論 0 5
  • 起床后喝了一大杯水吞下了昨晚的眼淚吞下了欲言又止的話然后我抹了抹嘴唇?jīng)]人知道我喝下了什么
    ukuk閱讀 192評論 0 0
  • 這兩天還在說辆影,這世界變化快,猜得著開頭黍特,猜不到結(jié)尾蛙讥。今天就真真是個意料之外的一天。 第一件事是去談一筆保證金灭衷。這個...
    米小喆閱讀 161評論 1 0