从JVM看Java语言特性(四) 接口和抽象类

从JVM看Java语言特性(四) 接口和抽象类

接口和抽象类都是上层抽象, 一个类可以实现多个接口却只能继承一个抽象类. 从上一篇文章中我们大致明白了继承和多态是如何实现的, 多态通过JVM在vtable放置不同的方法指针来决定到底是调用父类的方法还是子类的方法. 按照多态的这个思路的话, 接口应该就是一个指示器, 方便JVM来判断vtable里面需要哪些方法. 那么到底是不是这么回事儿我们目前还不知道, 这篇文章让我们探索一下接口和抽象类的一些细节.

1. 接口与抽象类的实现

如果一个接口里面有一个getA()方法的话, 它的方法字节码是这样的:

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

另一方面, 如果一个抽象类里面只有一个abstract getA()方法, 它的方法字节码是这样的:

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

可以看到, 抽象类有构造器, 如果里面没有抽象方法的话它和一般的没什么区别. 但是如果抽象类里有抽象方法的话, 那么这些抽象方法和接口里的抽象方法也几乎没有区别, 唯一的区别是抽象类的抽象方法可以是加上访问修饰符, 如public或protected, 也可以不加修饰符(给抽象方法加private会报错, 因为这样的话这个抽象方法永远无法被实现, 进而就没办法生成这个类的对象, 没有意义), 而接口只能为public(不加修饰符也会默认为public).

这样来看, 抽象类的结构像是一个普通类加上接口的抽象方法.

接口不能有构造器, 因为接口是一种规范, 类可以实现多个接口,若多个接口都有自己的构造器,则不好决定构造器链的调用次序.

2. 接口和抽象类的意义

传统多继承的复杂度一直是逃避不开的问题, 如果继承的两个父类里面包含了相同名称的方法或参量, 那么 JVM无法判断到底需要执行哪一种, 冲突问题就会非常明显. 由此, Java采用了单继承来避免这个问题, 为了提高灵活程度, 又引入接口来实现轻耦合的多继承. 接口用不会引起冲突的方式解决了多重继承的问题

接口的目的是实现, 它和实现类的关系是"has a", 也就是说接口可以是实现类的一个组件, 一个部分.

抽象类的目的则是继承, 它和实现类的关系是"is a", 其实完全可以不存在抽象类这个东西, 用一个普通类代替抽象类, 将所有抽象方法都改成空的实方法, 然后再让子类将其重写, 这也就成了变相的抽象类. 这样子想下去抽象类的唯一作用就是帮助程序员少犯错(抽象类无法实现, 只能多态, 减少程序员误操作的可能), 并且帮助程序员理顺继承关系(名字就叫抽象类, 一看就知道可能有多个类继承于它).

在知乎上看到的一句话说的特别好[传送门] "抽象类主要是用来抽象类别, 接口主要是用来抽象方法功能. 当你关注事物的本质的时候, 用抽象类; 当你关注一种操作的时候用接口",

3. 抽象类与接口中的参数

抽象类由于是一个类, 并且有构造函数, 所以它和一个普通类对参数的处理是一样的, 具体可见我的这篇文章, 因此这里就不展开了.

但是对于接口来说, 没有构造器的话接口是如何初始化它的数据的? 这个问题值得探讨. 我们假设有下面这个接口:

public interface FInterface {

    int a = 1;

    public static int c = 2;

    public static final int d = 1;

    int getA();

}

它的方法字节码是:

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

经过测试, 发现无论有没有public static final, 生成的class文件里都会有这三个关键词. 也就意味着接口里面所有的参数都是常量, 在类加载过程中都已经被解析并且被放进了常量池里.

4. jdk8 和 jdk9 中接口的变化

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

    default方法是指接口默认实现的方法, 主要是为了解决旧版本的兼容性问题, 以下是无default关键字和有default关键字的字节码对比:

     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
    
    

    可以发现default方法就是一个普通类里面实际存在的方法. 这样Java就实现了方法的多继承, 虽然设计的初衷并不是让使用者去滥用default, 只是一种为了不破坏之前代码的一种妥协.

    那如果两个接口都有相同的方法不就冲突了吗? 其实可以使用接口名+.super.+方法名来区分不同的方法, 就像下面这样:

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

    这样就不会引起冲突了. 但是Java为了让程序员少犯错误, 除非实现类重写了这个方法, 不允许实现的两个接口里有相同的default方法.

    除此之外, jdk9支持接口存在私有方法, 由于私有方法不存在子类直接调用, 因此也不会有冲突的问题, 这里就不再展开了.

总结

​ 为了写这一章, 本人查阅了大量资料, 包括了很多博客. 但是经过实践之后, 很多博客都有或多或少的错误, 比如有的说接口不能有静态方法(静态方法的解析是类加载的一部分, 当然可以有!), 我自己以后写博客的时候会尽量找官方或者权威的说法来论证, 不会轻易相信网上的东西.

​ 总的来说, 接口和抽象类是越来越相似了, 两者的主要区别在于接口可以实现多个和接口没有构造器.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 1,211评论 0 2
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,573评论 18 399
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 1,188评论 0 5
  • 起床后喝了一大杯水吞下了昨晚的眼泪吞下了欲言又止的话然后我抹了抹嘴唇没人知道我喝下了什么
    ukuk阅读 188评论 0 0
  • 这两天还在说,这世界变化快,猜得着开头,猜不到结尾。今天就真真是个意料之外的一天。 第一件事是去谈一笔保证金。这个...
    米小喆阅读 154评论 1 0