首先来思考一个问题:什么是“抽象”?
你可能会说抽象就是大体的框架、不是具体的、是飘渺的、概念的等等。没错,抽象的概念既然如此,所以抽象类其实就是:不能实例出对象的类。
在面向对象编程一开始,我们就提到过 OO 的特征:

  • 封装
  • 继承
  • 多态

至于“抽象”到底属不属于 OO 的特征,一直被争议,也就是说 OO 既有三大特征的说法,也有四大特征的说法。长期以来面向对象专家们在他们的论文、书籍、阐述中都只有封装、继承、多态。直到 2010 年左右,国内的软件公司面试题中出现,他们增加了“抽象” abstract。

假设当前存在这样的问题域:
图片2.png
Animal、Dog、Cat 类都可以从类得到具体实例,但父类 Animal 需要实例化吗?new Animal() 到底能得到一个什么实际动物?Dog 有 Dog 的叫法,Cat 有 Cat 的叫法,各有各的实现方式,那 Animal 作为父类还有必要书写实现方法吗?
答案是否定的,父类完全没有必要实现所有逻辑,也没有必要创建父类对象。此时就可以将父类的方法抽象出来,抽象类就诞生啦!
图片3.png
因此,当一个在实际问题域中没有产生对象的必要,而只是它的子类提供共有属性和方法,这种情况下就可以将其设计为抽象类

抽象类

抽象类本质上就是加了 abstract 修饰符的类,是不能被实例化的类。
语法:

  1. 访问修饰符 abstract class 类名{
  2. }

普通类所拥有的属性、构造、方法、静态代码块、内部类,在抽象类的内部也都拥有。其特点在于:

  1. 可以有抽象方法
  2. 不能产生对象,只能充当父类

    抽象方法

    在刚才的场景里出现了“父类存在某个方法,但需要子类去重写具体实现”的情况,但这里其实存在一个问题:一个方法是由 方法声明+方法实现 组成的,方法的实现其实就是 {} 里的内容。从编程语言的语法结构看来,只要书写了 {} 就表示方法已经实现,只不过其中的实现是“什么指令都不发出”。
    父类 Animal 确实有“叫”的行为,但如果不能确定具体实现那方法中也就不应该有 {}。所以对于只有方法的声明,没有方法的实现 的方法称为 抽象方法
    当一个类中存在抽象方法,则这个类也必须是抽象类
    语法:

    1. 访问修饰符 abstract 返回类型 函数名(参数列表); // 注意没有 {}
    1. public abstract class Pet{ // 抽象类
    2. public abstract void bark(); // 抽象方法
    3. }
    1. // Animal.java
    2. public abstract class Animal {
    3. public abstract void bark();
    4. }
    5. // Dog.java
    6. public class Dog extends Animal {
    7. public void bark() {
    8. System.out.println("汪汪!");
    9. }
    10. }
    11. // Cat.java
    12. public class Cat extends Animal {
    13. public void bark() {
    14. System.out.println("喵喵~");
    15. }
    16. }
    17. // TestMain.java
    18. public class TestMain {
    19. public static void main(String[] args) {
    20. new Dog().bark(); // 汪汪!
    21. new Cat().bark(); // 喵喵~
    22. new Animal(); // 报错
    23. }
    24. }

    抽象类抽象方法特点

  3. 抽象类和抽象方法都是用 abstract 修饰

  4. 抽象类不一定有抽象方法,理论上不要给没有抽象方法的类声明为抽象类。既然所有方法都有具体实现,就不“抽象”了
  5. 有抽象方法的类,就一定是抽象类
  6. 抽象类无法实例化
  7. 构造器不能为抽象的
  8. 静态方法不能为抽象
  9. abstract 和 final 互斥
    • 抽象类无法实例化,用于构建共有以便继承
    • 终态类可以
  10. 子类必须重写抽象类中所有抽象方法,否则它也是抽象类

抽象类既然不能产生对象,也就是说抽象类就是专门为子类规范:共有属性和行为,实际场景中使用的永远都是它的子类对象。就像 new Animal() 能得到具体得到猫?狗?兔子?
虽然抽象类不能产生对象,但它的子类是需要产生对象的,new 一个子类,必然要先调用父类的构造,得到共有属性和方法,所以抽象类的构造不是为自己用的,而是在产生子类对象时去被调用的。
抽象类也是类,也可以声明变量,只是无法产生对象。 Pet p; 可以,Pet p = new Pet() 就不行。可以赋值为子类的对象 Pet p = new Dog(); 这就体现了多态性。