继承与多态
理解 extends、super、方法重写、向上/向下转型
继承与多态
继承和多态是面向对象的两大基石。继承让子类复用父类的代码,多态让同一行代码能处理不同类型的对象。
学完本章你将: 掌握 extends、super、方法重写、向上/向下转型、Object 根类。
继承 —— 代码复用
继承的本质是 "is-a" 关系:Dog is an Animal(狗是动物)。子类拥有父类的所有属性和方法,还能添加自己的特色。
java
// 父类 —— 提取公共特性
public class Animal {
protected String name; // protected:子类可见
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 在吃东西");
}
}
// 子类 —— 继承父类 + 增加自己的内容
public class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造器(必须放第一行)
}
@Override // 注解表明"这是重写"
public void eat() { // 重写父类的 eat()
System.out.println(name + " 在吃骨头");
}
public void bark() { // 子类独有的方法
System.out.println(name + " 汪汪!");
}
}
⚠️ 注意: Java 是单继承,一个类只能有一个直接父类。但是可以通过接口实现多继承效果。
多态 —— 一个接口,多种形态
多态的核心是:父类引用可以指向子类对象。Animal a = new Dog() 这行代码中,变量 a 的类型是 Animal,但它实际指向的是 Dog 对象。调用 a.eat() 时,JVM 会在运行时找到实际对象的方法来执行,而不是看变量类型。这就是动态绑定。
java
// 向上转型 —— 自动,安全的(Dog 肯定是 Animal)
Animal animal = new Dog("旺财");
animal.eat(); // "旺财 在吃骨头" —— 调用的是 Dog 的 eat,不是 Animal 的!
// animal.bark(); // ❌ Animal 类型没有 bark() 方法
// 向下转型 —— 需要强制,可能失败
if (animal instanceof Dog) { // 先检查:animal 真的是 Dog 吗?
Dog dog = (Dog) animal; // 安全转型
dog.bark(); // "旺财 汪汪!"
}
多态的真正威力 —— 统一处理不同类型:
java
Animal[] zoo = {
new Dog("旺财"),
new Cat("咪咪"),
new Bird("小啾"),
};
// 不用管具体是什么动物,统一调用 eat()
for (Animal a : zoo) {
a.eat(); // 每个动物用自己的方式吃
}
// 输出:旺财 在吃骨头 / 咪咪 在吃鱼 / 小啾 在吃虫子
💡 什么时候用多态? 当你写代码时,如果发现自己在写很多
if (x instanceof Dog) ... else if (x instanceof Cat) ...,说明应该用多态重构成统一接口。
Object —— 所有类的祖先
Java 中每个类都隐式继承 Object。Object 提供了三个最常被覆盖的方法:
| 方法 | 默认行为 | 何时重写 |
|---|---|---|
| `toString()` | 返回 `类名@哈希值` | 需要打印有意义的对象信息 |
| `equals()` | 比较内存地址(等同 `==`) | 需要按内容比较两个对象 |
| `hashCode()` | 返回内存地址的哈希 | 重写 equals 时必须一起重写 |
java
public class User {
private String name;
private int age;
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 同一个引用
if (!(o instanceof User u)) return false; // 类型不匹配
return age == u.age && Objects.equals(name, u.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 与 equals 保持一致
}
}
⚠️ 黄金法则: 重写
equals必须同时重写hashCode,否则 HashMap/HashSet 会出 bug!