一、继承的本质:消灭重复代码
面向对象三大特性——封装、继承、多态——面试常问,却少有人一语道破继承的本质:代码复用。
public class Dog extends Animal {
public void wang() { }
}
Dog 一旦 extends Animal,就天然获得了父类所有非 private 的能力,无需 Ctrl+C / Ctrl+V。
二、Java 单根体系与 Object 三大常见覆写
所有类的终极父类是 java.lang.Object,因此任何对象都至少有:
- equals:默认比较地址,实际业务几乎必覆写。
- hashCode:与 equals 一起重写,保证在 HashMap/HashSet 中行为一致。
- toString:给调试日志一个可读字符串。
以 Order 为例:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return id.equals(((Order) o).id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
IDE 一键生成即可,手写既繁琐又易错。
三、== 与 equals 的区别
• == 比较“栈里的值”,对于对象即地址。
• equals 比较“业务意义上的内容”,可由我们自行定义。
四、类加载与初始化顺序
严格自顶向下:父类静态 → 子类静态 → 父类构造器 → 子类构造器。
如果父类没有无参构造,子类必须在第一行显式 super(args),否则编译失败。
五、实例方法的覆写(Override)
@Override
public void writeContent() {
super.writeContent(); // 想保留父逻辑再扩展
System.out.println("我的内容");
}
@Override 只是一个让编译器帮咱把关的注解,写错方法签名即报错。
六、模板方法设计模式实战
骨架在父类,变化点下沉到子类:
public abstract class BookWriter {
public final void writeBook() { // 模板
writeTitle();
writeContent();
writeEnding();
}
protected abstract void writeContent(); // 变化
}
Spring 的 refresh() 也是同理。
七、向上转型、向下转型与 instanceof
向上转型安全且自动:
Animal a = new Cat("Tom"); // 总是 OK
向下转型必须强转,可能抛 ClassCastException,先用 if (a instanceof Cat) 守护。
八、final 关键字的四种玩法
- 变量:值或引用不可变。
- 参数:禁止在方法体内重新赋值。
- 方法:禁止子类覆写。
- 类:禁止继承,如
final class World {}。
带来的好处:
• 线程安全(不可变对象天然并发友好)
• 运行期可内联,提升性能
• 设计层面“关闭修改”,避免子类破坏父类约定。
九、单例模式与 final 结合
public final class World {
public static final World INSTANCE = new World();
private World() { }
}
私有化构造器 + 公共静态常量 = 最简单线程安全单例。
十、组合优于继承
“is-a” 才用继承,“has-a” 用组合。
错误示范:继承 HashSet 实现 CountingSet,因父类 addAll 内部会调 add,导致计数翻倍。
正确做法:
public class CountingSet {
private final Set<Object> set = new HashSet<>();
private int count;
public boolean add(Object o) {
count++;
return set.add(o);
}
}
组合不会暴露父类细节,耦合度更低,测试也更容易。