返回
Featured image of post Java - 抽象类

Java - 抽象类

抽象类除了不能实例化对象之外,类的其它功能依然存在.

语法

抽象类中可以存在

  • 构造方法
  • 抽象方法
  • 非抽象方法
  • 成员变量
  • 静态成员变量

总结:除了不能被实例化,抽象类几乎具有普通类的所有特性。


注意点

  • 抽象类不能被实例化,如果试图实例化抽象类,编译无法通过

  • 抽象类中可以有构造方法,但是构造方法不能为抽象方法,其中原因见 [为什么构造函数不能为抽象]

    抽象类及其实现的构造方法也必须遵循一般继承中的构造方法规范,包括:

    • 子类实例化对象时,如果子类构造方法没有显式调用父类构造方法,默认调用 super()
    • 子类要使用父类有参构造方法,使用 super(...) 形式,且 super() 必须是子类构造方法中第一行语句
    • 父类没有不带参构造方法(只定义了有参构造方法而没有定义无参的),子类构造方法中必须显示调用父类其它构造方法,否则编译不过
  • 抽象类中的静态方法也不能为抽象方法

  • 抽象类中的抽象方法只能声明不能有具体实现,这与接口不同(接口方法在 JDK 1.8 后也可以有默认实现),具体原因见 [为什么抽象方法不能有实现而接口方法可以]


有端联想

抽象类总结了几个比较有嚼劲的问题

1. 为什么构造函数不能为抽象

Java 抽象函数 和 C++ 虚函数 是等价概念,因此这里直接从 C++ 的角度找答案了。结论就是——构造函数从语言和逻辑来看都不能为虚函数。

  • 从内存结构角度来看

    虚函数对应虚函数表 vtable ,表为类所有,但虚函数表指针为每个对象所有,在构造函数运行时进行空间分配,因此构造函数无法在未创建虚表指针的情况下调用虚表。

  • 从语言逻辑角度来看

    构造函数目的是初始化实例,我们知道抽象类和虚基类都没有实例化的需求,将构造函数定义为虚函数是没有意义的。

    可以先回顾一下虚函数的作用过程——通过指针或者引用来调用虚函数的时候能够调用到子类的对应成员函数。而构造函数是在创建对象时自动调用的,调用这一函数的指针或引用所对应的对象还不存在,也就决定了构造函数不能是虚函数。

2. 为什么抽象方法不能有实现而接口方法可以

JDK 1.8 以前两者都不能有实现,这对接口的实现造成了一些麻烦,因为很多时候我们希望接口方法能有一些默认实现,从而减少其实现类中的重复代码;更多的时候,我们实现接口也并不需要用到其中所有方法,但没有默认实现导致我们不得不在实现一个接口时实现其中所有方法(因此出现了很多用于适配接口与实现类的 Adapter 类),哪怕根本不会用到。综上,JDK 1.8 以后接口也可以有默认实现了。

这其实让我联想到数据库设计范式对数据库粒度的苛求。按照规范来说,如果有类在实现接口过程中存在用不到的方法,那说明接口的粒度仍不够小——对行为的定义不够细,但从另一方面来讲,追求完美的接口粒度又会使代码晦涩难懂,且不够灵活。

但是抽象类中从一开始就不存在这个问题——因为抽象类并不只能包含抽象方法,还能包含普通方法,而 Java 中普通方法本身就是“虚函数”,允许子类重写。那么如果想要一个默认实现,直接写普通方法就行了。Java 语言设计抽象类的目的本就是方便代码重用,但在设计接口之初并没有把这一需求囊括进来。

抽象方法,仅用于标识子类必须实现的方法(有一种特殊情况除外,就是子类也是抽象类)。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus