目录
1. 什么是抽象类?
2. 抽象类的特点
2.1 抽象方法
2.2 实例代码演示
步骤 1:创建抽象类 Animal
步骤 2:创建子类 Fly(鸟类)继承 Animal
步骤 3:创建子类 Run(哺乳动物)继承 Animal
步骤 4:测试类
3. 抽象类的实际应用场景
4. 接口:工程中的核心规范
4.1 什么是接口?
4.2 接口与抽象类的区别
4.3 为什么 Java 只允许单继承?
4.4 为什么接口不会出现菱形问题?
5. 接口的代码实践
5.1 定义接口
5.2 定义实现类
5.3 测试类
5.4 接口的多态应用
6. 总结
1. 什么是抽象类?
在 Java 中,被 abstract 关键词修饰的类,就叫做抽象类。
2. 抽象类的特点
- 不能直接实例化:不能使用 new 关键词直接创建对象,只能被子类继承使用。
- 代码复用与解耦:抽象类的功能和继承父类的作用相同——都是抽取公共代码,实现代码复用,从而减少耦合,提高工程效率。
2.1 抽象方法
抽象类有两个相关概念,其中一个是抽象方法:
- 只有方法的声明(只有方法名),没有方法体(没有方法的具体实现逻辑)。
- 特点是使用 abstract 关键词修饰。
- 只能存在于抽象类、接口中。
- 抽象类中可以有非抽象成员(不使用 abstract 关键词修饰的方法、属性)。
2.2 实例代码演示
下面通过一个完整的例子来理解抽象类的使用:
步骤 1:创建抽象类 Animal
// Animal.java
public abstract class Animal {
// 抽象方法:只有声明,没有方法体
public abstract void move();
// 非抽象方法
public void eat() {
System.out.println("动物正在进食…");
}
}
步骤 2:创建子类 Fly(鸟类)继承 Animal
// Fly.java
public class Fly extends Animal {
// 必须实现父类的抽象方法
@Override
public void move() {
System.out.println("鸟儿在天空中飞翔");
}
}
步骤 3:创建子类 Run(哺乳动物)继承 Animal
// Run.java
public class Run extends Animal {
@Override
public void move() {
System.out.println("哺乳动物在地上奔跑");
}
}
步骤 4:测试类
// Test.java
public class Test {
public static void main(String[] args) {
// 创建子类对象
Animal bird = new Fly();
Animal mammal = new Run();
// 调用方法
bird.move(); // 输出:鸟儿在天空中飞翔
bird.eat(); // 输出:动物正在进食…
mammal.move(); // 输出:哺乳动物在地上奔跑
mammal.eat(); // 输出:动物正在进食…
}
}
从测试结果可以看到,抽象类的使用和普通父类方法重写没有本质区别,但它强制子类必须实现特定的抽象方法,从而保证了代码的规范性。
3. 抽象类的实际应用场景
正如老师所说:“今天我讲完抽象类之后,除了你们 25 级软工的大二还要再学习一遍 JavaSE 的小部分知识以外,你这一辈子再也不会碰到抽象类了。(除非你很有想法,主动找一些工程源码或者看 Java 自带的 API)”
这说明了抽象类在实际工程开发中使用频率相对较低,更多出现在教学和底层框架设计中。但理解抽象类对于掌握面向对象编程思想至关重要。
4. 接口:工程中的核心规范
如果说抽象类在实际工程中用得很少,那么接口则是每个计算机从业者都必须掌握的概念。老师强调:“你只要是从事和计算机相关的行业,不管是前后端、全栈开发、测试、AI,百分之一千会用到接口。”
4.1 什么是接口?
让我们通过一个生动的故事来理解接口的作用:
有两家开发数据库的公司,他们垄断了全球所有数据库的市场。A 数据库公司开发了两种查询数据库的方法分别是:selectAll、selectNameById;公司 B 也开发了两种查询方法分别是 findAll、findNameById。
大壮是 A 企业的员工,他的女朋友也在 A 企业。大壮女朋友现在和大壮闹分手,她说:“我再也不想你出现在我的生活里”。那么大壮只能从 A 公司跳槽到 B 公司,可是他只会用 A 公司给出的两个方法,不会用 B 公司给出的方法,他不知道 B 公司给出的这些方法是干什么的,哪怕这两个方法的实现逻辑是相同的。
了解到这个情况,AB 两家公司的老总就给出了一个解决方案:既然我们这两家公司的查询方案都相同,为什么不统一一下查询方法的签名呢?于是两家公司将两种查询方法统一命名为 selectAll、selectNameById。
这就是接口的核心作用:用来规范和管理工程逻辑的命名。在实际开发中,我们先规定需要实现的方法以及这个方法是用来做什么的(定义接口),然后再来定义实现类来完成方法的具体实现逻辑(方法体)。
4.2 接口与抽象类的区别
听着是不是和抽象类差不多?那么抽象类和接口的区别在哪呢?
| 设计目的 | 代码复用,抽取公共代码 | 规范行为,定义契约 |
| 使用时机 | 在项目进展过程中抽取重复代码 | 在项目开展之前规定需要用的方法 |
| 继承/实现 | Java 只允许单继承 | 一个类可以实现多个接口 |
| 方法实现 | 可以有具体方法实现 | Java 8 前只能是抽象方法 |
4.3 为什么 Java 只允许单继承?
这是为了防止菱形问题(Diamond Problem):
class A {
public void eat() {
System.out.println("A eat");
}
}
class B extends A {
@Override
public void eat() {
System.out.println("B eat");
}
}
class C extends A {
@Override
public void eat() {
System.out.println("C eat");
}
}
// 如果允许多继承,会出现问题:
// class D extends B, C { }
// D 调用 eat() 时,编译器不知道该执行 B 的还是 C 的版本,产生歧义。
4.4 为什么接口不会出现菱形问题?
因为接口里默认都是抽象方法,没有方法体;哪怕多个接口有同名抽象方法,最终都由实现类自己重写一遍,实现类说了算,不会产生冲突。
5. 接口的代码实践
5.1 定义接口
// Function.java
public interface Function {
// 抽象方法(默认就是 public abstract)
void selectAll();
void selectNameById(int id);
}
5.2 定义实现类
建议将接口的实现类命名为:接口名 + Impl
// FunctionImpl.java
public class FunctionImpl implements Function {
@Override
public void selectAll() {
System.out.println("查询所有数据");
}
@Override
public void selectNameById(int id) {
System.out.println("根据ID查询名称,ID:" + id);
}
}
5.3 测试类
// TestInterface.java
public class TestInterface {
public static void main(String[] args) {
// 接口不能使用 new 创建对象,必须使用实现类的构造方法
Function func = new FunctionImpl();
// 调用接口方法
func.selectAll(); // 输出:查询所有数据
func.selectNameById(1001); // 输出:根据ID查询名称,ID:1001
}
}
5.4 接口的多态应用
既然可以使用接口的引用类型创建实际对象,那么就可以用到多态:
// 定义第二个接口
public interface Function2 {
void customMethod();
}
// 实现类实现多个接口
public class MultiFunctionImpl implements Function, Function2 {
@Override
public void selectAll() {
System.out.println("实现 selectAll");
}
@Override
public void selectNameById(int id) {
System.out.println("实现 selectNameById,ID:" + id);
}
@Override
public void customMethod() {
System.out.println("Function2 的自定义方法");
}
}
// 测试多态
public class TestPolymorphism {
public static void main(String[] args) {
Function func1 = new MultiFunctionImpl();
Function2 func2 = new MultiFunctionImpl();
// 接口约束了对象能够使用的方法
func1.selectAll(); // 正确
// func1.customMethod(); // 编译错误:Function 接口中没有 customMethod 方法
func2.customMethod(); // 正确
// func2.selectAll(); // 编译错误:Function2 接口中没有 selectAll 方法
}
}
6. 总结
抽象类和接口是 Java 面向对象编程中两个重要的概念,它们的核心区别在于:
在实际工程开发中,接口的使用频率远高于抽象类。理解它们的区别和适用场景,对于编写高质量、可维护的 Java 代码至关重要。





