元数据卡
- 前置知识:第8章(上)继承、第8章(中二)方法重写
- 预计时间:20 分钟
- 阅读模式:概念理解 + 对比
- 完成标志:能说清抽象类和接口的核心区别,知道何时用哪一个
你的进度
你现在有了 MessageSender——一个"消息发送器",它的 send() 方法有默认实现。但你总觉得不对:一个通用发送器根本没有意义,具体形式应该是马、鸽子或喇叭。而且如果有人继承 MessageSender 却忘了重写 send(),Bug 就潜伏了。
更麻烦的是,如果"侦察兵"也要能发消息,但它已经继承了 Soldier——Java 只有单继承。怎么办?
你的任务:让父类只定义"必须做什么",不提供默认实现。再把"能发消息"这个能力独立出来,让任何类都能拥有。
第一幕:抽象类——故事讲一半
抽象类用 abstract 关键字,把方法签名写出来,但不写方法体:
public abstract class AbstractMessageSender {
protected String message;
protected long timestamp;
public AbstractMessageSender(String message) {
this.message = message;
this.timestamp = System.currentTimeMillis();
}
public abstract void send(); // 抽象方法——没有方法体
public void logTime() { // 具体方法
System.out.println("[LOG] " + timestamp);
}
}做了两个改变:① 类声明加 abstract——不能 new了;② send() 去掉方法体加 abstract——子类必须实现。
忘记重写会编译报错:
// 编译错误:Pigeon is not abstract and does not override abstract method send()
public class Pigeon extends AbstractMessageSender {
public Pigeon(String message) { super(message); }
// 忘了重写 send()
}你试试:new AbstractMessageSender("hello")——编译器报错,抽象类不能实例化。
抽象类是"讲故事讲一半"——"我有 send 方法,但具体怎么做——你(子类)自己定。"
第二幕:接口——纯契约
抽象类还有一个矛盾:一个类只能 extends 一个父类。如果侦察兵已经继承了 Soldier,就不能再继承 AbstractMessageSender。
那能不能把"能发送消息"这个能力从"类"中独立出来,变成一种纯契约?
接口 (interface) 就是干这个的:
// Sendable.java — 接口定义
public interface Sendable {
void send(); // 抽象方法,默认 public abstract
}语言:Java 17+
任何类都可以实现这个接口:
public class Pigeon extends AbstractMessageSender implements Sendable {
public Pigeon(String message) { super(message); }
@Override
public void send() {
logTime();
System.out.println(" 信鸽起飞!消息: " + message);
}
}一个类只能有一个父类,但可以有多个接口:
public class Scout extends Soldier implements Sendable, Receivable, Trackable {
// ...
}这就是接口的强大之处——抽象了"能做什么",而不是"是什么"。
Java 8+ 接口的新能力
Java 8 之后,接口可以包含默认方法:
public interface Sendable {
void send(); // 抽象方法(必须实现)
default void logTime() { // 默认方法——接口里也可以有实现了
System.out.println("[LOG] " + System.currentTimeMillis());
}
static boolean isValidMessage(String msg) { // 静态方法
return msg != null && !msg.isEmpty();
}
}语言:Java 8+ 你试试:创建一个类实现 Sendable,但不重写 logTime()——看看是否能直接调用默认方法。
默认方法解决了"接口升级时所有实现类都要改"的问题。
第三幕:抽象类 vs 接口——怎么选?
| 对比维度 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class | interface |
| 构造器 | 可以有 | 不能有 |
| 字段 | 可以有各种访问级别 | 只能是 public static final(常量) |
| 方法实现 | 可以有具体方法 | Java 8+ 可以有 default 和 static 方法 |
| 继承限制 | 一个类只能继承一个 | 一个类可以实现多个 |
| 核心用途 | 共享状态 + 部分实现 | 定义能力契约 |
经验法则:如果两个类共享状态(字段)和部分实现 → 抽象类。如果只是定义能力契约 → 接口。
第四幕:Object 类——所有类的根
每个类都隐含地继承了 java.lang.Object,即使你没写 extends:
public class Horse { ... }
// 等价于:public class Horse extends java.lang.Object { ... }Object 提供了几个基本方法:
Object obj = new Horse("敌军", "张三");
obj.toString(); // "Horse@6d06d69c"
obj.equals(another); // 默认比较引用
obj.hashCode();
obj.getClass(); // 运行时获取实际类型重写 toString() 让调试更方便:
@Override
public String toString() {
return "Horse{message='" + message + "'}";
}旅人笔记
抽象类说"我有骨架,你来填"。 接口说"我只要你遵守契约"。 抽象类可以有构造器和状态,接口不能。 一个类实现多个接口,只继承一个父类。 所有类的老祖宗是
Object。
→ 下一站预告
你学会了接口和抽象类来定义契约。现在做一个综合练习巩固一下——然后开启函数式编程的大门,用 Lambda 把行为像包裹一样递过去。