一、回顾与本篇目标
上一篇你学会了类和对象的基本概念——类是一张设计图纸,对象是按照图纸造出来的实物。你知道了怎么定义类、怎么用 new 创建对象、怎么写构造方法、怎么通过 this 区分属性和参数。
面向对象编程有三大核心特性:封装、继承、多态。这三个概念是 Java 编程思想的精髓——理解它们,你才能真正写出结构清晰、易于维护、可复用的代码。
本篇的目标:
- 理解封装——把数据藏起来,通过方法访问
- 理解继承——子类复用父类的代码
- 学会方法重写——子类重新定义父类的方法
- 理解多态——同一个方法,不同的表现
- 掌握
super关键字——调用父类的构造和方法
二、封装——把数据保护起来
2.1 为什么要封装——生活中的例子
想象你有一台电视机。电视机内部有复杂的电路、电容、芯片——但你不需要直接操作这些。厂家给你一个遥控器,上面有开机、调音量、换台几个按钮。你只能通过遥控器来操作电视,不能直接伸手去碰里面的电路板。
为什么这样设计?两个原因:
- 保护内部零件:如果用户随便碰电路板,可能触电或者损坏电视。
- 简化操作:用户只需要知道按哪个按钮,不需要了解电视内部怎么工作。
在编程中,封装就是同样的道理——把对象的内部数据(属性)藏起来,只暴露有限的方法给外部使用。外部代码不能直接修改属性,只能通过方法操作——方法内部可以做验证,确保数据不会被错误地修改。
2.2 用 private 隐藏属性
用 private 关键字修饰属性,这些属性就只能在当前类内部访问,外部代码看不到也改不了。
public class BankAccount {
private String owner; // 私有属性:外部不能直接访问
private double balance; // 私有属性:外部不能直接访问
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = initialBalance;
}
// 公共方法:安全地存款
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
}
}
// 公共方法:安全地取款
public void withdraw(double amount) {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
}
}
// 公共方法:查询余额(只读)
public double getBalance() {
return this.balance;
}
public String getOwner() {
return this.owner;
}
}
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("张三", 1000.0);
// account.balance = 99999; // 编译错误!balance 是 private,外部不能直接访问
// account.owner = "黑客"; // 编译错误!owner 也是 private
// 正确的方式:通过公共方法操作
account.deposit(500.0); // 存款 500
account.withdraw(200.0); // 取款 200
System.out.println(account.getBalance()); // 1300.0
}
}
在这个例子中,balance 和 owner 被 private 保护起来了。外部代码只能通过 deposit()、withdraw()、getBalance() 这些公共方法来操作余额。这些方法内部做了验证——存款金额必须大于 0,取款不能超过余额。这样就保证了 balance 的值始终是合理的。
2.3 Getter 和 Setter——标准的访问方式
对于私有属性,通常提供一对公共方法来访问:
- Getter:读取属性的值。命名习惯:
get+ 属性名(首字母大写)。 - Setter:修改属性的值。命名习惯:
set+ 属性名(首字母大写)。Setter 内部可以做验证。
public class Student {
private String name;
private int age;
// Getter:获取姓名
public String getName() {
return this.name;
}
// Setter:设置姓名
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
// Getter:获取年龄
public int getAge() {
return this.age;
}
// Setter:设置年龄(带验证)
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
}
}
}
使用 getter 和 setter 的好处:
- 可以在 setter 中做验证:比如年龄不能是负数。
- 可以在 getter 中做格式化:比如返回日期时格式化显示。
- 可以只提供 getter 不提供 setter:让属性对外只读。
2.4 访问修饰符总结
Java 提供了四个访问修饰符,控制类、属性、方法在哪些地方可以访问:
| 修饰符 | 本类中 | 同包下 | 子类中 | 任何地方 |
|---|---|---|---|---|
| private | ✅ | ❌ | ❌ | ❌ |
| 默认(不写) | ✅ | ✅ | ❌ | ❌ |
| protected | ✅ | ✅ | ✅ | ❌ |
| public | ✅ | ✅ | ✅ | ✅ |
目前阶段,只记两个:private(只在本类内部使用)和 public(任何地方都能用)。另外两个(默认和 protected)后面用到的时候再学。
三、继承——子类复用父类的代码
3.1 为什么要继承——生活中的例子
想象你在描述动物:猫是动物,狗是动物,鸟也是动物。它们有一些共同的特征——都有名字、都会叫、都会吃东西。但它们又各有自己的特点——猫会抓老鼠,狗会看门,鸟会飞。
如果每描述一种动物,你都要把“有名字”“会吃东西”这些共同特征重新写一遍,那代码会非常重复。更合理的做法是:先把所有动物的共同特征定义在一个“动物”类里,然后让“猫”“狗”“鸟”继承这个类——自动获得所有共同特征,然后再加上自己独有的特征。
继承就是一个类获得另一个类的属性和方法。被继承的类叫父类(或超类),继承的类叫子类。
3.2 继承的基本语法——extends
用 extends 关键字表示继承:
// 父类——定义共同特征
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void eat() {
System.out.println(this.name + " 正在吃东西。");
}
public void sleep() {
System.out.println(this.name + " 正在睡觉。");
}
}
// 子类——继承父类,添加独有特征
public class Cat extends Animal {
public Cat(String name) {
super(name); // 调用父类的构造方法
}
public void meow() {
System.out.println(getName() + " 喵喵叫!");
}
}
// 另一个子类
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(getName() + " 汪汪叫!");
}
}
使用这些类:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("咪咪");
Dog dog = new Dog("旺财");
// 子类自动拥有父类的方法
cat.eat(); // 咪咪 正在吃东西。
cat.sleep(); // 咪咪 正在睡觉。
cat.meow(); // 咪咪 喵喵叫!
dog.eat(); // 旺财 正在吃东西。
dog.sleep(); // 旺财 正在睡觉。
dog.bark(); // 旺财 汪汪叫!
}
}
关键点:
Cat继承Animal后,自动拥有eat()和sleep()方法——不需要在 Cat 中重新写一遍。Cat还可以添加自己独有的方法meow()。- Java 只支持单继承:一个子类只能有一个直接父类。但父类也可以继承另一个父类,形成继承链。
3.3 super 关键字——调用父类的构造和方法
在上面的例子中,Cat 的构造方法里出现了 super(name)。这个 super 代表父类,super(name) 就是调用父类的构造方法。
重要规则:子类的构造方法中,必须调用父类的构造方法(通过 super(...)),而且必须是第一行。如果你不写,Java 会自动插入 super()(调用父类的无参构造方法)。但如果父类没有无参构造方法,编译就会报错。
public class Cat extends Animal {
public Cat(String name) {
super(name); // 必须写在第一行,调用父类的 Animal(String name)
}
}
super 还可以用来调用父类的方法:
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
super.eat(); // 先调用父类的 eat()
System.out.println(getName() + " 吃完后舔了舔爪子。"); // 再加上自己的行为
}
}
四、方法重写——子类重新定义父类的方法
子类继承父类的方法后,如果觉得父类的方法不够好,可以重新定义一个同名的方法——这叫方法重写。
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void makeSound() {
System.out.println(this.name + " 发出了声音。");
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
// 重写父类的 makeSound 方法
@Override
public void makeSound() {
System.out.println(this.name + " 喵喵叫!");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(this.name + " 汪汪叫!");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("咪咪");
Dog dog = new Dog("旺财");
cat.makeSound(); // 咪咪 喵喵叫!
dog.makeSound(); // 旺财 汪汪叫!
}
}
方法重写的规则:
- 子类方法的方法名、参数列表、返回类型必须和父类完全一样。
- 子类方法的访问权限不能比父类更严格(父类是 public,子类不能是 private)。
@Override是注解,写在重写的方法前面。它不是必须的,但强烈推荐写——它让编译器帮你检查是否真的重写了父类的方法(如果方法名拼错了,编译器会报错)。
五、多态——同一个方法,不同的表现
多态是面向对象编程中最核心、也最难理解的概念。它的字面意思是“多种形态”——同一个类型的引用变量,可以指向不同的子类对象,调用同一个方法时表现出不同的行为。
听起来很抽象,看代码就清楚了:
public class Main {
public static void main(String[] args) {
// 用父类类型的变量,引用子类对象
Animal a1 = new Cat("咪咪");
Animal a2 = new Dog("旺财");
// 调用同一个方法,实际执行的是子类重写后的版本
a1.makeSound(); // 咪咪 喵喵叫!
a2.makeSound(); // 旺财 汪汪叫!
}
}
关键理解:
a1的声明类型是Animal(父类),但它实际指向的对象是Cat(子类)。- 当你调用
a1.makeSound()时,Java 不是根据声明类型Animal来决定调用哪个方法,而是根据实际对象类型Cat来决定——这叫动态绑定。 - 所以
a1.makeSound()调用的是Cat的makeSound()(喵喵叫),a2.makeSound()调用的是Dog的makeSound()(汪汪叫)。
多态的威力——写更通用的代码
多态最大的价值在于:你可以写出处理父类类型的代码,但实际运行时会根据子类的不同表现出不同的行为。
public class Main {
// 这个方法接收 Animal 类型,但可以传入任何 Animal 的子类对象
public static void letAnimalSound(Animal animal) {
System.out.print("让动物叫一声:");
animal.makeSound();
}
public static void main(String[] args) {
Cat cat = new Cat("咪咪");
Dog dog = new Dog("旺财");
// 同一个方法,传入不同的子类对象,产生不同的行为
letAnimalSound(cat); // 让动物叫一声:咪咪 喵喵叫!
letAnimalSound(dog); // 让动物叫一声:旺财 汪汪叫!
}
}
letAnimalSound 方法的参数类型是 Animal。你可以传入 Cat、Dog、或者任何将来新写的 Animal 子类——这个方法不需要修改就能处理所有新的子类。这就是多态带来的灵活性和可扩展性。
六、综合演示——动物管理系统
下面这个示例综合运用了封装、继承、方法重写和多态:
// Animal.java —— 父类
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return this.name; }
public int getAge() { return this.age; }
public void eat() {
System.out.println(this.name + " 正在吃东西。");
}
public void makeSound() {
System.out.println(this.name + " 发出了声音。");
}
public void showInfo() {
System.out.println("名称:" + this.name + ",年龄:" + this.age + " 岁");
}
}
// Cat.java —— 子类
public class Cat extends Animal {
private String furColor; // 猫特有的属性:毛色
public Cat(String name, int age, String furColor) {
super(name, age);
this.furColor = furColor;
}
@Override
public void makeSound() {
System.out.println(getName() + " 喵喵叫!");
}
@Override
public void showInfo() {
super.showInfo(); // 调用父类的 showInfo
System.out.println(" 物种:猫,毛色:" + this.furColor);
}
public void catchMouse() {
System.out.println(getName() + " 抓住了一只老鼠!");
}
}
// Dog.java —— 子类
public class Dog extends Animal {
private String breed; // 狗特有的属性:品种
public Dog(String name, int age, String breed) {
super(name, age);
this.breed = breed;
}
@Override
public void makeSound() {
System.out.println(getName() + " 汪汪叫!");
}
@Override
public void showInfo() {
super.showInfo();
System.out.println(" 物种:狗,品种:" + this.breed);
}
public void guardHouse() {
System.out.println(getName() + " 在看家护院!");
}
}
// Main.java —— 测试
public class Main {
public static void main(String[] args) {
// 创建一个动物数组(利用多态)
Animal[] animals = new Animal[4];
animals[0] = new Cat("咪咪", 2, "橘色");
animals[1] = new Dog("旺财", 3, "金毛");
animals[2] = new Cat("小黑", 1, "黑色");
animals[3] = new Dog("大黄", 5, "中华田园犬");
System.out.println("===== 动物信息 =====");
for (Animal animal : animals) {
animal.showInfo();
System.out.println();
}
System.out.println("===== 动物叫声 =====");
for (Animal animal : animals) {
animal.makeSound(); // 多态:实际调用子类重写后的方法
}
System.out.println("\n===== 进食时间 =====");
for (Animal animal : animals) {
animal.eat(); // 所有动物共享父类的 eat 方法
}
}
}
输出:
===== 动物信息 =====
名称:咪咪,年龄:2 岁
物种:猫,毛色:橘色
名称:旺财,年龄:3 岁
物种:狗,品种:金毛
名称:小黑,年龄:1 岁
物种:猫,毛色:黑色
名称:大黄,年龄:5 岁
物种:狗,品种:中华田园犬
===== 动物叫声 =====
咪咪 喵喵叫!
旺财 汪汪叫!
小黑 喵喵叫!
大黄 汪汪叫!
===== 进食时间 =====
咪咪 正在吃东西。
旺财 正在吃东西。
小黑 正在吃东西。
大黄 正在吃东西。
七、本篇动手练习
练习 1:封装练习
新建 Product.java 和 ProductDemo.java。定义 Product 类,私有属性:名称(String)、价格(double)、库存(int)。提供构造方法和 getter/setter。在 setter 中验证:价格不能为负数,库存不能为负数。测试封装的验证是否生效。
练习 2:继承练习
新建 Vehicle.java(父类——交通工具,属性:品牌、速度,方法:move())、Car.java(子类——汽车,加一个 seatCount 座位数)、Bicycle.java(子类——自行车,加一个 hasBasket 是否有车篮)。测试继承关系。
练习 3:方法重写练习
在练习 2 的基础上,让 Car 和 Bicycle 分别重写 move() 方法,输出不同的移动描述(比如“汽车在公路上行驶”、“自行车在骑行道上行驶”)。
练习 4:多态练习
在练习 2 和 3 的基础上,创建一个 Vehicle 类型数组,放入 Car 和 Bicycle 对象。用循环遍历数组,调用每个对象的 move() 方法,观察多态的效果。
八、本篇小结
这一篇你学会了面向对象编程的三大核心特性:
- 封装:用
private隐藏属性,通过公共的 getter/setter 方法访问。setter 中可以加验证逻辑,保护数据不被错误修改。 - 继承:用
extends让子类获得父类的属性和方法。子类可以添加自己的属性和方法。super()调用父类构造方法(必须写在子类构造的第一行)。 - 方法重写:子类重新定义父类的方法(方法名、参数、返回类型完全相同)。用
@Override注解让编译器帮你检查是否正确重写。 - 多态:父类类型的变量可以引用子类对象。调用方法时,Java 根据实际对象类型决定执行哪个版本(动态绑定)。多态让你写出更通用、更灵活的代码。
封装、继承、多态是 Java 面向对象编程的基石。理解了它们,你就能用面向对象的思想来设计和组织代码。下一篇,我们学习 Java 中最常用的两个工具类——String 和 ArrayList,它们在日常开发中无处不在。
下一篇预告
下一篇——《字符串与集合——String 和 ArrayList》:String 类的常用方法(长度、截取、替换、查找、比较)、StringBuilder 高效拼接字符串、ArrayList 灵活的动态数组、集合的遍历和常用操作。
Java 零基础入门,每周更新。












暂无评论内容