抽象类与接口
掌握 abstract、interface、default 方法、函数式接口
抽象类与接口
抽象类和接口都是用来"定义规范"的——它们告诉子类"你必须做什么",但不规定"怎么做"。区别在于:抽象类描述"是什么"(is-a),接口描述"能做什么"(can-do)。
学完本章你将: 掌握 abstract 类、interface 接口、default 方法、选择策略。
抽象类 —— 不完整的类
如果父类的方法无法给出合理实现(比如"每个图形的面积算法不同"),就该用 abstract 声明为抽象方法。有抽象方法的类必须是抽象类,抽象类不能 new。
java
public abstract class Shape { // abstract 类
protected String color;
public Shape(String color) { // 抽象类可以有构造方法
this.color = color;
}
public abstract double area(); // 抽象方法——没有方法体,子类必须实现
public void display() { // 普通方法——子类直接继承
System.out.println(color + " 图形,面积:" + area());
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color); // 调用抽象类的构造方法
this.radius = radius;
}
@Override
public double area() { // 必须实现抽象方法
return Math.PI * radius * radius;
}
}
什么时候用抽象类? 多个类有共同的属性和方法,但又没法完全通用的场景。
接口 —— 行为契约
接口是一组方法的"合同"——实现接口就是签了合同,承诺一定提供这些方法。重点是**"能做什么"而不是"是什么"**。一个类可以实现多个接口,弥补了 Java 单继承的限制。
java
// 接口定义"能飞"和"能游泳"
public interface Flyable {
void fly(); // 默认 public abstract
}
public interface Swimmable {
void swim();
}
// Duck 既会飞又会游泳——同时实现两个契约
public class Duck implements Flyable, Swimmable {
@Override
public void fly() { System.out.println("鸭子飞行中"); }
@Override
public void swim() { System.out.println("鸭子游泳中"); }
}
default 方法 —— 接口也能有实现
Java 8 开始接口可以有 default 方法。目的是:不破坏已有实现类的前提下给接口增加新方法。
java
public interface Logger {
// 旧方法——子类必须实现
void log(String msg);
// 新加的 default 方法——子类不想重写也能直接用
default void logWithTimestamp(String msg) {
log("[" + java.time.LocalTime.now() + "] " + msg);
}
}
怎么选?一个判断标准
问自己一个问题:这些类本质上是同一种东西吗?
| 判断 | 用抽象类 | 用接口 |
|---|---|---|
| 有共享的成员变量 | ✅ | ❌(只能 static final) |
| 有共享的构造逻辑 | ✅ | ❌ |
| 它们不是同一种东西,只是能做同一件事 | ❌ | ✅ |
| 需要多继承效果 | ❌ | ✅ |
Car 和 Bicycle 都是 Vehicle → 抽象类 Vehicle
Car 和 Plane 都能载客 → 接口 Carryable
Bird 和 Plane 都能飞 → 接口 Flyable