OOP 进阶
重载/覆盖、封装、对象生命周期。
30 分钟
OOP 进阶
这一课你将能做到
- 一句话区分重载与覆盖,并判断任意一个方法调用发生的是编译期绑定还是运行期绑定
- 用 final 字段、构造器校验、不可变视图写出「外部改不坏」的领域类
- 说清 equals 与 hashCode 的契约,以及抽象类与接口的选型标准
为什么要学
写 CRUD 时感觉不到 OOP,代码要活过三个月时它才开始伤人:Service 里塞满 public 字段、任何人都能把订单状态改成任意值、内部 List 引用被外层 clear() 掉——这些不是「设计美学」问题,是线上事故的源头。
面试角度:重载 vs 覆盖、equals/hashCode 契约、抽象类 vs 接口,是 Java 面试出场率最高的三连问,本课答案按能背的标准写。Spring 的 Bean 体系(第 9 课)也建立在这些概念上:单例 Bean 为什么要求无状态,就是对象生命周期知识的直接应用。
先建立图像
重载 overload:同一个类里,同名不同参,编译期拍板
save(Lesson) ┐
save(Lesson, boolean) ┘ 编译器按参数列表选一个
覆盖 override:子类重新实现父类的同名同参方法,运行期拍板
Animal a = new Dog();
a.speak(); ← 声明类型是 Animal,实际执行的是 Dog.speak()
对象生命周期:
new → 堆上分配 → 被引用持有(局部变量/字段/静态字段/容器)
↓
所有引用断开(出栈、置 null、容器移除)
↓
不可达 → GC 择机回收(时机由 JVM 决定,不可控)
关键直觉:GC 看的是「引用链是否可达」,不是「你觉得它没用了」。静态 Map 只进不出,就是最典型的内存泄漏。
跟着做
步骤 1:完整示例——接口、抽象类、封装、equals/hashCode
新建 OopDemo.java,完整可运行:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class OopDemo {
/** 接口:定义「能力」,谁都能实现 */
interface Payable {
boolean pay(long amountFen);
default String channelName() { return getClass().getSimpleName(); }
}
/** 抽象类:抽取公共骨架,模板方法固定流程 */
static abstract class PaymentChannel implements Payable {
protected final String merchantId;
protected PaymentChannel(String merchantId) {
this.merchantId = Objects.requireNonNull(merchantId);
}
/** 模板方法:流程固定为 final,差异留给子类 */
public final String payAndLog(long amountFen) {
boolean ok = pay(amountFen);
return channelName() + (ok ? " 支付成功 " : " 支付失败 ") + amountFen + " 分";
}
}
static class WechatPay extends PaymentChannel {
WechatPay(String merchantId) { super(merchantId); }
@Override
public boolean pay(long amountFen) {
return amountFen > 0; // 模拟调用微信下单接口
}
}
/** 封装:对外只读,内部列表不外泄可变引用 */
static class Order {
private final long id;
private final List<String> skuCodes = new ArrayList<>();
Order(long id) { this.id = id; }
void addSku(String sku) {
if (sku == null || sku.isBlank()) throw new IllegalArgumentException("sku 非法");
skuCodes.add(sku);
}
public long id() { return id; }
/** 不可变视图:外部 clear() 会抛 UnsupportedOperationException */
public List<String> skuCodes() { return Collections.unmodifiableList(skuCodes); }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order other)) return false;
return id == other.id;
}
@Override
public int hashCode() { return Objects.hash(id); }
}
public static void main(String[] args) {
PaymentChannel channel = new WechatPay("m-1001");
System.out.println(channel.payAndLog(9900)); // 运行期绑定到 WechatPay.pay
Order order = new Order(1L);
order.addSku("SKU-1");
System.out.println(order.skuCodes());
try {
order.skuCodes().clear();
} catch (UnsupportedOperationException e) {
System.out.println("封装生效:外部无法清空内部列表");
}
System.out.println(new Order(1L).equals(new Order(1L))); // true:按业务键判等
}
}