首先来思考一个问题:什么是“抽象”?
你可能会说抽象就是大体的框架、不是具体的、是飘渺的、概念的等等。没错,抽象的概念既然如此,所以抽象类其实就是:不能实例出对象的类。
在面向对象编程一开始,我们就提到过 OO 的特征:
- 封装
- 继承
- 多态
至于“抽象”到底属不属于 OO 的特征,一直被争议,也就是说 OO 既有三大特征的说法,也有四大特征的说法。长期以来面向对象专家们在他们的论文、书籍、阐述中都只有封装、继承、多态。直到 2010 年左右,国内的软件公司面试题中出现,他们增加了“抽象” abstract。
假设当前存在这样的问题域:
Animal、Dog、Cat 类都可以从类得到具体实例,但父类 Animal 需要实例化吗?new Animal()
到底能得到一个什么实际动物?Dog 有 Dog 的叫法,Cat 有 Cat 的叫法,各有各的实现方式,那 Animal 作为父类还有必要书写实现方法吗?
答案是否定的,父类完全没有必要实现所有逻辑,也没有必要创建父类对象。此时就可以将父类的方法抽象出来,抽象类就诞生啦!
因此,当一个类在实际问题域中没有产生对象的必要,而只是为它的子类提供共有属性和方法,这种情况下就可以将其设计为抽象类。
抽象类
抽象类本质上就是加了 abstract
修饰符的类,是不能被实例化的类。
语法:
访问修饰符 abstract class 类名{
}
普通类所拥有的属性、构造、方法、静态代码块、内部类,在抽象类的内部也都拥有。其特点在于:
- 可以有抽象方法
-
抽象方法
在刚才的场景里出现了“父类存在某个方法,但需要子类去重写具体实现”的情况,但这里其实存在一个问题:一个方法是由
方法声明+方法实现
组成的,方法的实现其实就是{}
里的内容。从编程语言的语法结构看来,只要书写了{}
就表示方法已经实现,只不过其中的实现是“什么指令都不发出”。
父类 Animal 确实有“叫”的行为,但如果不能确定具体实现那方法中也就不应该有{}
。所以对于只有方法的声明,没有方法的实现 的方法称为 抽象方法。
当一个类中存在抽象方法,则这个类也必须是抽象类。
语法:访问修饰符 abstract 返回类型 函数名(参数列表); // 注意没有 {}
public abstract class Pet{ // 抽象类
public abstract void bark(); // 抽象方法
}
// Animal.java
public abstract class Animal {
public abstract void bark();
}
// Dog.java
public class Dog extends Animal {
public void bark() {
System.out.println("汪汪!");
}
}
// Cat.java
public class Cat extends Animal {
public void bark() {
System.out.println("喵喵~");
}
}
// TestMain.java
public class TestMain {
public static void main(String[] args) {
new Dog().bark(); // 汪汪!
new Cat().bark(); // 喵喵~
new Animal(); // 报错
}
}
抽象类抽象方法特点
抽象类和抽象方法都是用 abstract 修饰
- 抽象类不一定有抽象方法,理论上不要给没有抽象方法的类声明为抽象类。既然所有方法都有具体实现,就不“抽象”了
- 有抽象方法的类,就一定是抽象类
- 抽象类无法实例化
- 构造器不能为抽象的
- 静态方法不能为抽象
- abstract 和 final 互斥
- 抽象类无法实例化,用于构建共有以便继承
- 终态类可以
- 子类必须重写抽象类中所有抽象方法,否则它也是抽象类
抽象类既然不能产生对象,也就是说抽象类就是专门为子类规范:共有属性和行为,实际场景中使用的永远都是它的子类对象。就像 new Animal() 能得到具体得到猫?狗?兔子?
虽然抽象类不能产生对象,但它的子类是需要产生对象的,new 一个子类,必然要先调用父类的构造,得到共有属性和方法,所以抽象类的构造不是为自己用的,而是在产生子类对象时去被调用的。
抽象类也是类,也可以声明变量,只是无法产生对象。 Pet p;
可以,Pet p = new Pet()
就不行。可以赋值为子类的对象 Pet p = new Dog();
这就体现了多态性。