Java 继承
继承(Inheritance)是面向对象第二大特性——子类继承父类,自动获得父类的字段和方法,避免重复代码。Java 是单继承:一个类只能有一个直接父类,但通过"接口"可以实现"多继承"的效果。本章带你彻底搞懂 Java 继承。
1. extends 关键字
子类用 extends 关键字继承父类,获得父类所有非 private 成员:
// 父类(基类、超类)
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 在吃东西");
}
public void sleep() {
System.out.println(name + " 在睡觉");
}
}
// 子类用 extends 继承父类
// 子类自动获得父类的所有非 private 成员
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造方法,必须放第一行
}
// 子类可以新增自己的方法
public void bark() {
System.out.println(name + " 汪汪叫!");
}
}
public class InheritDemo {
public static void main(String[] args) {
Dog d = new Dog("旺财");
d.eat(); // 旺财 在吃东西(继承自父类)
d.sleep(); // 旺财 在睡觉(继承自父类)
d.bark(); // 旺财 汪汪叫!(子类特有)
}
}几个核心规则:
- 单继承:一个类只能
extends一个父类(不像 C++ 多继承)。 - 所有类的根:Java 所有类隐式继承
Object,即使没写extends Object。 - 子类构造必须先调父类构造:用
super(...),不写则编译器自动插入super()(无参)。 - 子类不能继承父类的 private 成员、构造方法(但能通过 super 调用)。
2. 方法重写(@Override)
子类可以重新实现父类的方法,这叫"重写"(Override)。运行时根据实际对象类型调用对应版本——这就是多态的基础。
class Shape {
public double area() {
return 0;
}
// 父类方法,子类可以重写
public String describe() {
return "形状,面积 = " + area();
}
}
class Circle extends Shape {
private double r;
public Circle(double r) { this.r = r; }
// @Override 注解:告诉编译器"我这是重写父类方法"
// 写错了(方法名拼错、参数对不上)编译器会报错
@Override
public double area() {
return Math.PI * r * r;
}
}
class Rectangle extends Shape {
private double w, h;
public Rectangle(double w, double h) {
this.w = w;
this.h = h;
}
@Override
public double area() {
return w * h;
}
}
public class OverrideDemo {
public static void main(String[] args) {
Shape s1 = new Circle(2);
Shape s2 = new Rectangle(3, 4);
System.out.println(s1.describe()); // 形状,面积 = 12.566...
System.out.println(s2.describe()); // 形状,面积 = 12.0
}
}重写的规则("两同两小一大"):
- 方法名相同、参数列表相同。
- 返回值类型小于等于父类(子类可以返回更具体的类型,即"协变返回")。
- 抛出异常小于等于父类。
- 访问权限大于等于父类(父类 public,子类不能改 protected)。
@Override 注解不是必须的,但强烈推荐写——它让编译器帮你检查"是不是真的重写了",新手救星。
3. super 关键字
super 是"父类的引用",有三种用法:
super(...):调用父类构造方法(必须放第一行)。super.方法():调用父类被重写的方法(在子类里想用父类版本时)。super.字段:访问父类字段(少用)。
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person 构造被调用");
}
public void greet() {
System.out.println("我是 Person: " + name);
}
}
class Student extends Person {
String school;
public Student(String name, int age, String school) {
super(name, age); // 调用父类构造(必须第一行)
this.school = school;
System.out.println("Student 构造被调用");
}
@Override
public void greet() {
super.greet(); // 调用父类版本
System.out.println("我是 Student,在 " + school + " 上学");
}
}
public class SuperDemo {
public static void main(String[] args) {
Student s = new Student("小明", 20, "清华");
// 输出:
// Person 构造被调用
// Student 构造被调用
// (创建子类对象时,父类构造先执行)
s.greet();
// 输出:
// 我是 Person: 小明
// 我是 Student,在 清华 上学
}
}构造方法链:创建子类对象时,先执行父类构造(一直追溯到 Object),再执行子类构造。这就是为什么 super(...) 必须放第一行——父类必须先初始化。
4. Object 类:所有类的根
Java 所有类都继承自 java.lang.Object。Object 提供了几个每个类都该重写的方法:
// 所有类都隐式继承 Object,即使没写 extends Object
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 Object 的 equals:自定义"相等"逻辑
@Override
public boolean equals(Object o) {
if (this == o) return true; // 同一对象
if (o == null || getClass() != o.getClass()) return false;
User u = (User) o;
return age == u.age && name.equals(u.name);
}
// 重写 hashCode:equals 相等的对象 hashCode 必须相等
@Override
public int hashCode() {
return java.util.Objects.hash(name, age);
}
// 重写 toString:打印对象时更友好
@Override
public String toString() {
return "User{name=" + name + ", age=" + age + "}";
}
}
public class ObjectDemo {
public static void main(String[] args) {
User u1 = new User("小明", 20);
User u2 = new User("小明", 20);
System.out.println(u1); // User{name=小明, age=20}(默认调 toString)
System.out.println(u1.equals(u2)); // true(自定义相等)
System.out.println(u1 instanceof User); // true
}
}toString():打印对象时调用,默认返回类名@hashcode,应该重写成可读形式。equals(Object):默认比较地址(==),应该重写成"内容相等"。hashCode():重写 equals 必须同时重写 hashCode(契约:equals 相等的对象 hashCode 必须相等)。getClass():返回运行时类信息(不能重写,final)。
IDEA 一键生成:右键 Generate → equals() and hashCode() → toString()。这是新手必学操作。
5. final 关键字:禁止继承/重写
final 用在三个地方,含义不同:
- final 类:不能被继承(如
String、Integer都是 final,防止被改写破坏不可变性)。 - final 方法:不能被子类重写(如
Object.getClass())。 - final 变量:只能赋值一次(常量)。
// final 类:不能被继承
final class MathUtil {
public static int square(int n) { return n * n; }
}
// class SubMath extends MathUtil { } // ❌ 编译报错:final 类不能被继承
class Parent {
// final 方法:不能被子类重写
public final void rules() {
System.out.println("这是规矩,不许改");
}
public void optional() {
System.out.println("可以重写");
}
}
class Child extends Parent {
// @Override
// public void rules() { } // ❌ 编译报错:final 方法不能重写
@Override
public void optional() {
System.out.println("子类重写了");
}
}6. 继承 vs 组合
继承虽好,但不要滥用。《Effective Java》建议:优先使用组合而非继承。
- is-a 关系用继承:Dog is an Animal —— Dog 继承 Animal 合理。
- has-a 关系用组合:Car has an Engine —— Car 不应继承 Engine,而应把 Engine 当字段。
- 继承的子类和父类是强耦合,父类改一个方法,所有子类都受影响。组合则灵活得多。
小结
这一章你掌握了 Java 继承的全部要点:extends、super、方法重写、Object 根类、final。下一篇我们看继承的延伸——多态,看父类引用怎么调用子类方法,以及它为什么是设计模式、框架扩展的基石。
← 上一篇 Java 类与对象
下一篇 Java 多态 →