继承性是面向对象编程三大特性之一(封装、继承、多态),它允许我们基于已有类创建新类。这种机制完美模拟了现实世界中"子承父业"的关系模式。想象一下生物学的分类系统:哺乳动物继承自动物,灵长类又继承自哺乳动物。每个下级分类都自动获得上级分类的全部特征,同时可以发展自己的独特属性。
在Java中,继承通过extends关键字实现。被继承的类称为父类(或超类、基类),继承的类称为子类(或派生类)。子类会自动获得父类的所有非私有成员(属性和方法),就像儿子天然继承父亲的姓氏和部分基因特征。
重要提示:Java是单继承语言,一个类只能直接继承一个父类。这种设计避免了C++多继承带来的"菱形继承"问题,虽然牺牲了部分灵活性,但大幅降低了复杂度。
继承的语法结构非常简单直观:
java复制class ParentClass {
// 父类成员定义
}
class ChildClass extends ParentClass {
// 子类特有成员
}
让我们用一个电商系统的例子具体说明:
java复制// 父类:商品基础信息
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public void displayInfo() {
System.out.println("商品名称:" + name + ",价格:" + price);
}
}
// 子类:电子产品(继承自Product)
class Electronics extends Product {
private String brand;
private int warrantyMonths;
public Electronics(String name, double price, String brand, int warranty) {
super(name, price); // 调用父类构造器
this.brand = brand;
this.warrantyMonths = warranty;
}
@Override
public void displayInfo() {
super.displayInfo(); // 复用父类方法
System.out.println("品牌:" + brand + ",保修期:" + warrantyMonths + "个月");
}
}
当创建子类对象时,构造器的调用遵循特定规则:
java复制// 父类
class Animal {
private String species;
public Animal(String species) {
this.species = species;
System.out.println("Animal构造器执行");
}
}
// 子类
class Dog extends Animal {
private String breed;
public Dog(String species, String breed) {
super(species); // 必须显式调用,因为父类没有无参构造器
this.breed = breed;
System.out.println("Dog构造器执行");
}
}
方法重写是继承体系中最强大的特性之一,它允许子类重新定义从父类继承的方法。要正确重写方法,必须遵守以下规则:
java复制class Shape {
public Shape draw() {
System.out.println("绘制形状");
return this;
}
}
class Circle extends Shape {
@Override
public Circle draw() { // 协变返回类型
System.out.println("绘制圆形");
return this;
}
}
@Override注解虽然不是语法要求,但强烈建议始终使用,因为:
java复制class Parent {
public void show() {
System.out.println("Parent show");
}
}
class Child extends Parent {
@Override // 如果没有这个注解,shoow()会被当作新方法
public void show() {
System.out.println("Child show");
}
}
Java的四个访问级别在继承中表现不同:
| 修饰符 | 类内部 | 同包 | 子类 | 任意位置 |
|---|---|---|---|---|
| private | √ | × | × | × |
| default | √ | √ | ×(不同包) | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
重点说明protected:
java复制// 父类
package com.example.base;
public class Vehicle {
protected String licensePlate;
}
// 子类
package com.example.car;
import com.example.base.Vehicle;
public class Car extends Vehicle {
public void setPlate(String plate) {
this.licensePlate = plate; // 可以访问protected成员
}
}
super关键字主要有三种使用场景:
java复制class Parent {
protected int value = 10;
public void print() {
System.out.println("Parent value: " + value);
}
}
class Child extends Parent {
private int value = 20;
@Override
public void print() {
System.out.println("Child value: " + value);
System.out.println("Parent value: " + super.value);
super.print(); // 调用父类方法
}
}
继承虽然强大,但滥用会导致设计僵化。适合使用继承的场景包括:
设计警示:如果关系是"has-a"而非"is-a",应该使用组合而非继承。例如,Car has an Engine(组合),而非Car is an Engine(错误继承)。
这是面向对象设计的重要原则,由Barbara Liskov提出:
"子类对象必须能够替换父类对象,而不影响程序的正确性。"
具体表现为:
违反LSP的典型例子:
java复制class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
}
class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // 破坏了矩形长宽可独立设置的约定
}
@Override
public void setHeight(int h) {
super.setWidth(h); // 同上
super.setHeight(h);
}
}
当继承关系不明确时,优先考虑组合(对象作为字段):
java复制// 使用继承
class LoggingArrayList<E> extends ArrayList<E> {
@Override
public boolean add(E e) {
System.out.println("添加元素:" + e);
return super.add(e);
}
}
// 使用组合(更灵活)
class LoggingList<E> {
private final List<E> list;
public LoggingList(List<E> list) {
this.list = list;
}
public boolean add(E e) {
System.out.println("添加元素:" + e);
return list.add(e);
}
// 可以只暴露需要的List方法
}
final关键字可以用于:
java复制final class UtilityClass { // 不能被继承
private UtilityClass() {} // 防止实例化
public static final double PI = 3.14159; // 常量
public static final void utilityMethod() { // 不能重写
System.out.println("工具方法");
}
}
抽象类是不完整的模板,特点包括:
java复制abstract class Graphic {
protected int x, y;
public Graphic(int x, int y) {
this.x = x;
this.y = y;
}
public abstract void draw(); // 抽象方法
public void moveTo(int newX, int newY) {
this.x = newX;
this.y = newY;
}
}
class Circle extends Graphic {
private int radius;
public Circle(int x, int y, int radius) {
super(x, y);
this.radius = radius;
}
@Override
public void draw() {
System.out.printf("在(%d,%d)绘制半径为%d的圆\n", x, y, radius);
}
}
类加载和对象初始化的顺序非常重要:
java复制class Parent {
static { System.out.println("父类静态块"); }
{ System.out.println("父类实例块"); }
public Parent() { System.out.println("父类构造器"); }
}
class Child extends Parent {
static { System.out.println("子类静态块"); }
{ System.out.println("子类实例块"); }
public Child() { System.out.println("子类构造器"); }
}
// 输出顺序:
// 父类静态块 → 子类静态块 → 父类实例块 → 父类构造器 → 子类实例块 → 子类构造器
常见滥用场景及解决方案:
| 问题类型 | 症状 | 解决方案 |
|---|---|---|
| 过深继承 | 继承层级超过3层 | 考虑组合或接口 |
| 功能混杂 | 子类被迫继承不需要的方法 | 接口分离原则 |
| 脆弱基类 | 父类修改影响所有子类 | 封装变化,减少父类修改 |
| 菱形问题 | 需要多继承(Java不支持) | 使用接口+组合 |
重写equals()和hashCode()时的注意事项:
正确实现示例:
java复制class Point {
private int x, y;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
class ColorPoint extends Point {
private Color color;
@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
if (!(o instanceof ColorPoint)) return false; // 保持对称性
ColorPoint cp = (ColorPoint) o;
return color.equals(cp.color);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), color);
}
}
序列化继承体系时的特殊考虑:
java复制class Person {
private String name;
public Person() {} // 无参构造器
// getters/setters
}
class Employee extends Person implements Serializable {
private transient double salary; // 不序列化
private Department dept;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeUTF(getName()); // 手动序列化父类字段
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
setName(ois.readUTF()); // 手动反序列化父类字段
}
}
Java集合框架是展示继承优势的典型案例:
code复制Collection (接口)
↑
AbstractCollection (抽象类)
↑
List (接口)
↑
AbstractList (抽象类)
↑
ArrayList (具体类)
这种设计实现了:
Java异常体系完全基于继承:
code复制Throwable
↑
Error (如OutOfMemoryError)
↑
Exception
↑
RuntimeException (未检查异常)
↑
其他检查异常 (如IOException)
设计特点:
java复制class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode code) {
super(code.getMessage());
this.errorCode = code;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
Java I/O流展示了继承的强大扩展能力:
code复制InputStream (抽象类)
↑
FileInputStream
FilterInputStream
↑
BufferedInputStream
DataInputStream
这种设计允许:
Java 8引入接口默认方法,影响了传统的继承设计:
java复制interface Flyable {
default void fly() {
System.out.println("默认飞行方式");
}
}
interface Swimmable {
default void swim() {
System.out.println("默认游泳方式");
}
}
class Duck implements Flyable, Swimmable {
// 可以重写默认方法
@Override
public void fly() {
System.out.println("鸭子扑腾翅膀飞行");
}
// 也可以直接使用默认实现
}
Java 15引入的密封类提供了更精细的继承控制:
java复制public sealed class Shape
permits Circle, Square, Rectangle { // 只允许这三个子类
// 类定义
}
public final class Circle extends Shape { ... } // 不能再被继承
public non-sealed class Square extends Shape { ... } // 开放继承
public sealed class Rectangle extends Shape permits TransparentRectangle { ... }
密封类的优势:
Java 16引入的记录类对继承有特殊限制:
java复制public record Point(int x, int y) {
// 隐式final类,不能extends其他类
// 可以implements接口
}
// 不能这样做:
class SubPoint extends Point { ... } // 编译错误
记录类的设计哲学:
Java方法调用类型及性能特点:
| 调用类型 | 指令 | 性能 | 说明 |
|---|---|---|---|
| 静态调用 | invokestatic | 最快 | 静态方法 |
| 特殊调用 | invokespecial | 快 | 构造器、private方法、super调用 |
| 虚调用 | invokevirtual | 一般 | 实例方法(最常见) |
| 接口调用 | invokeinterface | 较慢 | 接口方法 |
| 动态调用 | invokedynamic | 可变 | Lambda、方法引用 |
JVM通过虚方法表(vtable)优化继承方法调用,每个类维护一个方法指针数组,快速定位实际方法实现。
对象在内存中的布局受继承影响:
示例内存布局:
code复制Object实例:
[对象头][空] // Object没有实例字段
String实例:
[对象头][Object字段][hash缓存][value引用][coder等] // String继承Object
JIT编译器会尝试方法内联优化,继承可能影响:
优化建议:
经典继承应用,定义算法骨架,子类实现具体步骤:
java复制abstract class DataParser {
// 模板方法(final防止子类修改算法结构)
public final void parse() {
readData();
processData();
writeOutput();
}
protected abstract void readData();
protected abstract void processData();
protected void writeOutput() {
System.out.println("输出结果...");
}
}
class CsvParser extends DataParser {
@Override
protected void readData() {
System.out.println("读取CSV数据");
}
@Override
protected void processData() {
System.out.println("解析CSV数据");
}
}
通过继承实现功能扩展:
java复制abstract class Coffee {
abstract double cost();
abstract String desc();
}
class SimpleCoffee extends Coffee {
double cost() { return 1.0; }
String desc() { return "普通咖啡"; }
}
abstract class CoffeeDecorator extends Coffee {
protected final Coffee decorated;
public CoffeeDecorator(Coffee c) {
this.decorated = c;
}
}
class WithMilk extends CoffeeDecorator {
public WithMilk(Coffee c) { super(c); }
double cost() { return decorated.cost() + 0.5; }
String desc() { return decorated.desc() + ",加牛奶"; }
}
通过继承实现对象创建的解耦:
java复制abstract class Logistics {
public void planDelivery() {
Transport t = createTransport();
t.deliver();
}
abstract Transport createTransport();
}
class RoadLogistics extends Logistics {
@Override
Transport createTransport() {
return new Truck();
}
}
class SeaLogistics extends Logistics {
@Override
Transport createTransport() {
return new Ship();
}
}
组合是继承的强大替代方案:
java复制// 使用继承
class Stack<E> extends ArrayList<E> {
public void push(E e) { add(e); }
public E pop() { return remove(size()-1); }
}
// 使用组合(更安全)
class Stack<E> {
private final List<E> list = new ArrayList<>();
public void push(E e) { list.add(e); }
public E pop() { return list.remove(list.size()-1); }
public boolean isEmpty() { return list.isEmpty(); }
}
现代Java中,接口可以替代部分继承场景:
java复制interface Flyable {
default void fly() {
System.out.println("默认飞行方式");
}
}
interface Swimmable {
default void swim() {
System.out.println("默认游泳方式");
}
}
class Duck implements Flyable, Swimmable {
// 可以组合多个行为
}
通过接口实现运行时行为变化:
java复制interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("信用卡支付:" + amount);
}
}
class ShoppingCart {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy s) {
this.strategy = s;
}
public void checkout(int amount) {
strategy.pay(amount);
}
}
测试继承类时的策略:
java复制abstract class AbstractTest {
protected abstract Shape createShape();
@Test
void testCommonBehavior() {
Shape s = createShape();
s.move(10, 20);
assertEquals(10, s.getX());
}
}
class CircleTest extends AbstractTest {
@Override
protected Shape createShape() {
return new Circle(0, 0, 5);
}
@Test
void testCircleSpecific() {
Circle c = new Circle(0, 0, 5);
assertEquals(78.5, c.area(), 0.1);
}
}
使用Mock框架测试继承类时的技巧:
java复制abstract class Database {
abstract Connection getConnection();
}
class MyDatabase extends Database {
@Override
Connection getConnection() {
return DriverManager.getConnection(...);
}
}
@Test
void testWithMock() {
Database mockDb = mock(Database.class);
when(mockDb.getConnection()).thenReturn(mock(Connection.class));
// 测试依赖Database的代码
}
继承可能导致的测试盲区:
建议:
常用调试技巧:
java复制void debugInheritance(Object obj) {
System.out.println("对象类型:" + obj.getClass());
System.out.println("父类:" + obj.getClass().getSuperclass());
System.out.println("接口:" + Arrays.toString(obj.getClass().getInterfaces()));
if (obj instanceof ParentType) {
System.out.println("是ParentType的实例");
}
}
追踪继承方法调用的方法:
java复制class Parent {
void method() {
System.out.println("Parent.method");
new Throwable().printStackTrace(); // 打印调用栈
}
}
class Child extends Parent {
@Override
void method() {
System.out.println("Child.method");
super.method();
}
}
调试构造器调用顺序的技巧:
java复制class Parent {
static { System.out.println("Parent静态块"); }
{ System.out.println("Parent实例块"); }
Parent() { System.out.println("Parent构造器"); }
}
class Child extends Parent {
static { System.out.println("Child静态块"); }
{ System.out.println("Child实例块"); }
Child() {
System.out.println("Child构造器开始");
// 调试此处
System.out.println("Child构造器结束");
}
}
Java的模式匹配特性(instanceof增强)简化继承处理:
java复制// 传统方式
if (shape instanceof Circle) {
Circle c = (Circle) shape;
System.out.println("半径:" + c.getRadius());
}
// 模式匹配
if (shape instanceof Circle c) {
System.out.println("半径:" + c.getRadius());
}
Project Valhalla引入的值类型可能影响继承:
未来可能改进泛型与继承的交互:
java复制// 目前限制
class Box<T> { ... }
// class IntBox extends Box<int> { ... } // 目前不支持
// 未来可能支持值类型特化
class IntBox extends Box<int> { ... }