理解 JAVA 抽象类和接口
最近刷力扣犯懒想写一个大项目里面放每道题的解答,遇到了很多语法方面的知识,理解后遂写成博客。笔者也是初学者,文章难免存在问题和稚嫩的地方。敬请各位斧正(抱拳)。
一、什么是抽象类和接口
1. 抽象类
抽象类是 JAVA 中的一个基本概念。简单来说就是用 abstract
关键字修饰,可以有构造函数但不能被实例化,可以有抽象函数也可以有实体函数的类。
抽象类用于描述“是什么”的关系,是一种 is-a
关系。它被用来定义一组具有共同特征的对象,并且可以包含实现共用的部分代码。
(以前看别人博客遇到这种不讲基本概念的我都会骂一句,现在自己写了发现也不想写基本概念Orz)
2. 接口
接口同样是 JAVA 中的基本概念,与抽象类不同,接口不使用 class
关键字声明,而使用 interface
声明。
接口不能拥有构造函数(自然也不能被实例化),也不能拥有实体函数。
接口用于描述“能做什么”的关系,是一种 can-do
关系。它定义了一组动作或能力,而不关心谁实现这些动作。接口更注重行为的抽象。
二、有什么用
尽管抽象类和接口最重要的作用都是描述语义,对代码复用的贡献较小(甚至从代码复用的角度看,接口还需要白白写一个文件)。但二者在多态中都能起到重要作用。
什么?你说实体类也可以?那我只好搬出语义了。
二、区别是什么
1. 抽象类和接口
抽象类是 class
,可以定义方法实现,看似比像个目录一样的接口高级多了,好像没什么可比性。
但在“继承”和多态中可以看出,二者是非常相似的。
抽象类定义了继承者的“性质”,而接口定义了实现者的“功能”。
继承者的“本质”在语义上可以独立存在,拥有实例化的能力(但不一定拥有实例化的价值),因此抽象类可以拥有构造函数和其他实体方法。与此同时,由于任何一个实例的本着明确且单一,抽象类才被设计为类,只允许单继承。
相反,继承者的“功能”不存在独立存在的语义,也不具备实例化的能力,他更像是一个模板,规定了他的实现者有某些方面的功能,却不指定功能的细节。因此接口没有构造函数和任何实体方法。同时,一个实例可以包含多种多样的特性(比如同时可以移动也可以比较大小),因此接口允许“多继承”。
抽象类和接口更像是在分管一个实例的“本质”和“特性”,而“本质”和“特性”的区别决定了抽象类和接口的区别。
实际应用示例
抽象类: 假设你在设计一个动物园管理系统,需要描述各种动物:
public abstract class Animal { public abstract void makeSound(); // 每种动物发出不同的声音 public void sleep() { System.out.println("This animal is sleeping."); } } public class Dog extends Animal { @Override public void makeSound() { System.out.println("Woof!"); } }
接口: 假设你需要让一些动物具备飞行的能力,可以定义一个
Flyable
接口:public interface Flyable { void fly(); } public class Bird extends Animal implements Flyable { @Override public void makeSound() { System.out.println("Chirp!"); } @Override public void fly() { System.out.println("This bird is flying."); } }
在这个例子中,
Bird
类同时继承了Animal
抽象类(因为鸟是一种动物),并实现了Flyable
接口(因为鸟会飞)。
2. 抽象类和实体类
关于子类“本质”的提取,其实抽象类和实体类都可以完成。二者的选用基本取决于语义。
如果系统中父类足够抽象以至于不能或没有意义以实例的形式出现,那就适合使用抽象类来描述,这样能够能清晰的描述类的设计结构。
而如果系统中的父类不足够抽象,父类以实例的形式出现对系统有一些作用,那就更适合用实体类来定义父类,这样一般更加有利于代码复用。
实际应用实例
实体父类:假设你在设计一个动物园管理系统,需要统计动物的数量。你可以用实体类来定义
Animal
。抽象父类:假设你在设计一个动物园管理系统,需要统计饲料的数量。这时适合用抽象类来定义
Animal
。因为饲料至少要区分食草动物和食肉动物,Animal
这一概念不再具有实例化的意义。