封装、继承、多态
一、封装
把数据和行为包在一起,隐藏内部细节,只暴露必要接口。
1、封装的作用:
保护数据,防止被随意修改
外部只需知道"怎么用",不用管"怎么实现"
改内部实现不影响外部调用。
2、怎么实现
属性私有化:用 private 修饰
java
class Student {
// 属性私有化
private String name;
private int age;
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
提供公共方法:getter / setter 或业务方法
java
// getter方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
// setter方法(含校验)
public void setName(String name) {
this.name = name;
}
在方法里做校验:保证数据合法
java
public void setAge(int age) {
if (age > 0 && age < 150) {
this.age = age;
} else {
System.out.println("年龄不合法!");
}
}
二、继承
子类自动拥有父类的属性和方法,实现代码复用
1、作用
复用:父类写好的东西,子类直接用
扩展:子类可以加自己的新功能
重写:子类可以改父类的行为
2、如何使用
① 创建父类
java
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// 父类的通用方法 ------ 子类会【重写】来修改行为
public String speak() {
return name + " 发出了声音";
}
// 父类的通用方法 ------ 子类直接【继承使用】
public String info() {
return name + "," + age + " 岁";
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + info();
}
}
② 创建子类 ------ 使用 extends 继承父类
java
// Dog 继承 Animal,自动拥有 name、age 属性和 info() 方法
public class Dog extends Animal {
// 子类独有的属性
private String breed; // 品种
// 子类构造器:用 super 调用父类构造器
public Dog(String name, int age, String breed) {
super(name, age); // 先让父类初始化自己的部分
this.breed = breed; // 再初始化子类独有的部分
}
// ======== 修改父类行为:重写 speak() ========
@Override
public String speak() {
return name + " 汪汪汪!"; // 不再是"发出了声音",而是具体的狗叫
}
// ======== 子类增加新功能:子类独有的方法 ========
public String fetch() {
return name + " 去捡球啦!";
}
// 子类也可以扩展 toString(),在父类基础上追加品种信息
@Override
public String toString() {
return super.toString() + ",品种:" + breed;
}
}
再写一个子类 Cat,展示不同的重写效果:
java
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
// Cat 对 speak() 做不同的重写
@Override
public String speak() {
return name + " 喵喵喵~";
}
// Cat 独有的新方法
public String climb() {
return name + " 爬上了屋顶!";
}
}
③ 调用 ------ 创建子类实例,体验继承、重写、扩展
java
public class Test {
public static void main(String[] args) {
// --- 1. 创建子类实例 ---
Dog dog = new Dog("旺财", 3, "柴犬");
Cat cat = new Cat("咪咪", 2);
// --- 2. 直接使用父类继承过来的方法 ---
System.out.println(dog.info()); // 输出:旺财,3 岁
System.out.println(cat.info()); // 输出:咪咪,2 岁
// --- 3. 调用被重写的方法 ------ 子类行为覆盖父类 ---
System.out.println(dog.speak()); // 输出:旺财 汪汪汪!
System.out.println(cat.speak()); // 输出:咪咪 喵喵喵~
// 如果没有重写 speak(),就会执行父类的 "旺财 发出了声音"
// --- 4. 调用子类独有的新方法 ---
System.out.println(dog.fetch()); // 输出:旺财 去捡球啦!
System.out.println(cat.climb()); // 输出:咪咪 爬上了屋顶!
// --- 5. 多态:父类引用指向子类对象 ---
Animal a1 = new Dog("小黑", 5, "拉布拉多");
Animal a2 = new Cat("花花", 1);
System.out.println(a1.speak()); // 输出:小黑 汪汪汪!(运行时实际是 Dog)
System.out.println(a2.speak()); // 输出:花花 喵喵喵~(运行时实际是 Cat)
}
}
3、要点小结
| 场景 | 怎么做 | 关键代码 |
|---|---|---|
| 继承父类 | 类声明加 extends 父类名 |
class Dog extends Animal |
| 调用父类构造器 | 构造器第一行写 super(...) |
super(name, age) |
| 修改父类行为(重写) | 方法上加 @Override,方法签名不变 |
public String speak() { ... } |
| 增加子类功能 | 直接写新属性 / 新方法 | private String breed; / public String fetch() |
| 调用父类被重写的方法 | 在子类中用 super.方法名() |
super.toString() |
三、多态
同一个方法调用,根据对象的实际类型,执行不同的行为。简单说:一个接口,多种实现。
1、作用
统一调用,代码简洁
加新类型时,不用改调用方代码
调用方只依赖父类(抽象),不依赖具体子类
2、如何使用
前提:沿用上面继承章节写好的
Animal(父类)、Dog和Cat(子类),且子类都重写了speak()。
① 核心写法:父类引用指向子类对象(向上转型,自动完成)
java
public class TestPolymorphism {
public static void main(String[] args) {
Animal a1 = new Dog("旺财", 3, "柴犬"); // 左边是父类,右边是子类
Animal a2 = new Cat("咪咪", 2);
// 同一个调用 a.speak(),执行哪个版本由"对象的真实类型"决定
System.out.println(a1.speak()); // 输出:旺财 汪汪汪!------ 真实对象是 Dog
System.out.println(a2.speak()); // 输出:咪咪 喵喵喵~ ------ 真实对象是 Cat
}
}
口诀:编译看左边,运行看右边。
- 编译时:
a1的类型是Animal,所以Animal里必须有speak(),否则编译报错; - 运行时:JVM 看真实对象是
Dog还是Cat,自动执行对应子类重写的版本(动态绑定)。
② 多态的好处:统一处理一批不同子类
java
Animal[] animals = {
new Dog("小黑", 5, "拉布拉多"),
new Cat("花花", 1),
new Dog("大黄", 4, "土狗")
};
// 不用 if/else 判断类型,同一句代码处理所有动物
for (Animal a : animals) {
System.out.println(a.speak());
}
③ 方法参数用父类,调用方可以传任意子类
java
// 调用方只依赖父类 Animal,不依赖任何具体子类
public static void makeSpeak(Animal a) {
System.out.println(a.speak());
}
// 传 Dog、Cat 都可以
makeSpeak(new Dog("豆豆", 2, "泰迪")); // 输出:豆豆 汪汪汪!
makeSpeak(new Cat("雪球", 1)); // 输出:雪球 喵喵喵~
④ 加新子类,调用方代码一行都不用改
java
// 以后新增一个 Pig 子类,上面的循环和 makeSpeak() 原封不动就能用
public class Pig extends Animal {
public Pig(String name, int age) {
super(name, age);
}
@Override
public String speak() {
return name + " 哼哼哼~";
}
}
makeSpeak(new Pig("佩奇", 3)); // 输出:佩奇 哼哼哼~
这就是「开闭原则」:对扩展开放(加新子类),对修改封闭(不用改老代码)。
⑤ 注意:父类引用不能直接调用子类独有方法
java
Animal a1 = new Dog("旺财", 3, "柴犬");
// a1.fetch(); // 编译报错!Animal 类型里没有 fetch()
// 必须先 instanceof 判断真实类型,再向下转型(强制转换)
if (a1 instanceof Dog) {
Dog d = (Dog) a1; // 向下转型
System.out.println(d.fetch()); // 输出:旺财 去捡球啦!
}
向下转型有风险:真实对象不是该类型时会抛 ClassCastException,所以转型前一定要用 instanceof 判断。
3、小结
多态的三大前提
| 要素 | 作用 |
|---|---|
| 继承 | 提供类型关系 |
| 重写 | 提供差异化实现 |
| 父类引用 | 提供统一的调用入口 |
| 三者齐备 | 才构成多态 |
封装、继承、多态的关系
| 关系 | 说明 |
|---|---|
| 封装 → 继承 | 封装让父类的内部实现对外不可见,子类只能通过受保护的接口来扩展,保证了继承的安全性 |
| 继承 → 多态 | 没有继承(或接口实现),就没有"父类引用指向子类对象"这种统一类型的基础 |
| 封装 → 多态 | 封装保证了方法签名的稳定性,多态调用时才能依赖统一的接口契约 |
| 多态 ← 继承 + 重写 | 多态是继承和方法重写共同作用的结果 |