Java 面向对象编程:类、继承与多态

FreeGuideOnline 最新 2026-06-12

Java 面向对象编程:类、继承与多态完全指南

前言

Java 是一种纯粹的面向对象编程语言,类 (Class)对象 (Object) 是其核心构建块。理解封装、继承和多态是掌握 Java 的必经之路。本教程将从零开始,用大量代码示例带您系统学习这三块核心内容,毫无保留地展示背后的设计思想与最佳实践。

1. 类与对象:一切从模型开始

1.1 什么是类?

类是现实世界事物的蓝图模板,它定义了对象将拥有的属性(字段)和行为(方法)。把类想象成“汽车设计图”,它规定了汽车应有的特征,但还不是一辆真正的汽车。

1.2 创建一个简单的类

public class Car {
    // 属性(字段)
    String color;
    String model;
    int year;

    // 行为(方法)
    void startEngine() {
        System.out.println(model + " 引擎启动!");
    }

    void showInfo() {
        System.out.println("车型: " + model + ", 颜色: " + color + ", 年份: " + year);
    }
}

1.3 从类创建对象

类本身不能直接使用,必须通过 new 关键字实例化为对象。每个对象拥有类中定义的独立数据副本。

public class Main {
    public static void main(String[] args) {
        // 创建 Car 类的对象
        Car myCar = new Car();

        // 为对象的属性赋值
        myCar.color = "蓝色";
        myCar.model = "Tesla Model 3";
        myCar.year = 2023;

        // 调用对象的方法
        myCar.showInfo();
        myCar.startEngine();
    }
}

运行结果:

车型: Tesla Model 3, 颜色: 蓝色, 年份: 2023
Tesla Model 3 引擎启动!

1.4 构造方法:让对象创建更优雅

如果每创建一个对象都要逐行赋值,代码会非常繁琐。构造方法 (Constructor) 可以在创建对象的同时完成初始化。

public class Car {
    String color;
    String model;
    int year;

    // 构造方法(名称必须与类名相同,无返回类型)
    public Car(String color, String model, int year) {
        this.color = color;   // this 指代当前对象
        this.model = model;
        this.year = year;
    }

    // 方法省略...
}

现在创建对象可以一步到位:

Car myCar = new Car("红色", "BMW X5", 2022);

1.5 封装:保护数据的护城河

直接将字段暴露为 public 非常危险,外部代码可以随意设置不合法的值。封装 要求将字段设为 private,并通过公有的 getter/setter 方法控制访问。

public class Car {
    private String color;   // 私有字段
    private String model;
    private int year;

    // 构造器...

    // Getter 方法
    public String getColor() {
        return color;
    }

    // Setter 方法(可加入验证逻辑)
    public void setColor(String color) {
        if (color != null && !color.isEmpty()) {
            this.color = color;
        } else {
            System.out.println("颜色不能为空");
        }
    }
    // 其他 getter/setter...
}

为什么封装很重要? 它让类的内部实现对使用者透明,可以随时修改内部逻辑而不影响外部代码,同时保证了数据的完整性。

2. 继承:代码复用的利器

2.1 继承的概念

继承允许一个类获得另一个类的属性和方法,并在此基础上进行扩展。被继承的类称为父类 (Superclass)基类,继承者称为子类 (Subclass)。Java 中使用 extends 关键字。

// 父类:载具
public class Vehicle {
    private String brand;
    private int speed;

    public Vehicle(String brand) {
        this.brand = brand;
        this.speed = 0;
    }

    public void accelerate(int increment) {
        speed += increment;
        System.out.println(brand + " 加速,当前速度:" + speed);
    }

    public String getBrand() {
        return brand;
    }
}

// 子类:电动汽车,继承 Vehicle
public class ElectricCar extends Vehicle {
    private int batteryLevel;

    public ElectricCar(String brand, int batteryLevel) {
        super(brand);          // 调用父类构造器
        this.batteryLevel = batteryLevel;
    }

    // 子类独有的方法
    public void charge(int amount) {
        batteryLevel += amount;
        System.out.println("充电完成,电量:" + batteryLevel + "%");
    }
}

2.2 使用继承体系

public class Main {
    public static void main(String[] args) {
        ElectricCar tesla = new ElectricCar("Tesla", 80);
        tesla.accelerate(50);  // 继承自 Vehicle
        tesla.charge(15);      // ElectricCar 自己的方法
        System.out.println("品牌:" + tesla.getBrand());
    }
}

运行结果:

Tesla 加速,当前速度:50
充电完成,电量:95%
品牌:Tesla

2.3 super 关键字与重写

super 可以调用父类的构造方法、方法和字段。方法重写 (Override) 允许子类提供父类方法的具体实现。

public class ElectricCar extends Vehicle {
    // ... 其他代码

    @Override   // 注解表明重写,编译器会检查签名是否正确
    public void accelerate(int increment) {
        // 调用父类逻辑
        super.accelerate(increment);
        // 添加电动车的特殊行为
        batteryLevel -= 2;
        System.out.println("电池消耗,剩余电量:" + batteryLevel + "%");
    }
}

2.4 Java 继承的核心规则

  • 单继承:一个 Java 类只能有一个直接父类(但可以通过接口实现多重继承的效果,稍后介绍)。
  • 构造器调用链:子类构造器必须调用父类构造器(显式或隐式),确保父类部分先于子类初始化。
  • 访问控制private 成员不能被子类直接访问,但 protected 可以跨包被子类使用。

3. 多态:同一行为的不同表现

多态是面向对象最强大的特性,它允许父类引用指向子类对象,并调用被子类重写的方法,从而实现“同一消息,多种形态”。

3.1 向上转型 (Upcasting)

将子类对象赋值给父类引用是自动且安全的,这就是向上转型。

Vehicle v = new ElectricCar("NIO", 90);
v.accelerate(30);   // 实际调用的是 ElectricCar 重写后的方法

此时,v 的编译时类型是 Vehicle,但运行时类型是 ElectricCar。方法调用由 JVM 根据实际对象类型动态绑定。

3.2 多态的经典演示

public class Zoo {
    public static void main(String[] args) {
        // 父类数组存储各种子类对象
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Bird();

        for (Animal a : animals) {
            a.makeSound();  // 每个对象发出自己独特的声音
        }
    }
}

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪!");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵!");
    }
}

class Bird extends Animal {
    @Override
    public void makeSound() {
        System.out.println("啾啾!");
    }
}

输出:

汪汪!
喵喵!
啾啾!

无论数组里的对象具体是什么类型,只要它们都是 Animal 的子类,我们都能用统一的方式调用 makeSound(),而 Java 会运行时选择正确的方法版本。

3.3 向下转型与 instanceof

需要将父类引用转回子类类型以访问子类特有方法时,必须使用强制类型转换,并先用 instanceof 检查。

Animal a = new Dog();
if (a instanceof Dog) {
    Dog dog = (Dog) a;
    dog.wagTail();   // 调用 Dog 独有的方法
}

盲目转型会抛出 ClassCastException,因此养成先判断的习惯。

3.4 抽象类与接口:为了多态而生

想让“动物”这个类不被直接实例化,只作为通用类型存在?抽象类abstract 声明。

abstract class Animal {
    public abstract void makeSound();  // 抽象方法,没有方法体
    public void breathe() {            // 普通方法
        System.out.println("呼吸中...");
    }
}

Animal 不能再 new,只能被继承。子类必须实现所有抽象方法。

接口 (Interface) 则更进一步,完全定义了行为契约。

interface Flyable {
    void fly();   // 默认 public abstract
}

class Bird extends Animal implements Flyable {
    @Override
    public void makeSound() {
        System.out.println("啾啾!");
    }

    @Override
    public void fly() {
        System.out.println("小鸟在飞翔");
    }
}

多态同样适用于接口:

Flyable f = new Bird();
f.fly();  // 输出:小鸟在飞翔

4. 综合案例:图形绘制系统

让我们将类、继承、封装、多态整合到一个完整例子中。

// 抽象父类
abstract class Shape {
    private String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    public String getColor() { return color; }
    
    public abstract double area();        // 面积计算,交给子类实现
    public abstract void draw();          // 绘制行为
}

// 子类:圆形
class Circle extends Shape {
    private double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制一个" + getColor() + "圆形,面积:" + area());
    }
}

// 子类:矩形
class Rectangle extends Shape {
    private double width, height;
    
    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double area() {
        return width * height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制一个" + getColor() + "矩形,面积:" + area());
    }
}

// 测试多态
public class DrawingApp {
    public static void main(String[] args) {
        Shape[] shapes = new Shape[3];
        shapes[0] = new Circle("红色", 3.0);
        shapes[1] = new Rectangle("蓝色", 4.0, 5.0);
        shapes[2] = new Circle("绿色", 1.5);
        
        for (Shape s : shapes) {
            s.draw();          // 多态调用,自动选择正确版本的 draw()
            System.out.println("---");
        }
    }
}

运行结果:

绘制一个红色圆形,面积:28.274333882308138
---
绘制一个蓝色矩形,面积:20.0
---
绘制一个绿色圆形,面积:7.0685834705770345
---

通过这个例子你可以看到,父类引用 + 方法重写是 Java 多态的精髓。它让程序具有良好的扩展性:将来增加三角形、梯形等新形状,只需新建子类并实现 area()draw(),调用方代码无需任何修改。

5. 总结与最佳实践

  • 设计类时要高内聚、低耦合,仅暴露必要的公开接口。
  • 多用组合,少用继承:如果只是代码复用,优先考虑将对象作为成员(组合),只有确切的“is-a”关系时再用继承。
  • 面向接口编程:使用抽象类和接口声明引用类型,代码更灵活、可测试。
  • 覆盖方法时务必加上 @Override 注解,可以避免拼写错误导致的重载而非重写。
  • 理解动态绑定机制:Java 虚拟机在运行时根据对象的实际类型来决定调用哪个方法,这是多态的基础。

掌握了类、继承和多态,你就真正踏入了 Java 面向对象的大门。继续练习,尝试用这些概念重构你以前的程序,你会逐渐体会到 OOP 带来的优雅与力量。