Java 面向对象编程:类、继承与多态
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 带来的优雅与力量。