封装继承多态

面向对象三大特性概述

Java 面向对象的核心就三个词:封装、继承、多态

类比理解:

封装:把东西锁在保险箱里,只留一个小窗口给你操作(保护数据)

继承:儿子继承老爸的财产和基因,但可以有自己的特长(代码复用)

多态:同一句”叫”,狗汪汪叫、猫喵喵叫(同一个方法,不同表现)

建议先学完 类与对象,再来看这篇

封装(Encapsulation)

一句话理解:把字段藏起来,只通过方法来访问——就像 ATM 机,你不能直接伸手进去拿钱,必须通过按钮操作

为什么要封装

数据验证:setter 里可以加判断逻辑,比如年龄不能是负数

安全性:外部不能随便改内部数据

灵活性:内部实现可以随便改,只要接口不变,外部代码不受影响

从安全角度理解:封装 = 最小权限原则

只暴露必要的接口,隐藏一切不需要暴露的细节

就像公司的门禁系统——不是每个人都有服务器房间的钥匙,你只能进你该进的地方

这也是软件安全设计的基本思想之一

怎么做封装

第一步:字段用 private 修饰(锁起来)

第二步:提供 public 的 getter 和 setter 方法(留一个小窗口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class BankAccount {
private String owner;
private double balance; // 余额,private 外部不能直接改

public BankAccount(String owner, double balance) {
this.owner = owner;
if (balance >= 0) {
this.balance = balance;
}
}

// getter:只读
public double getBalance() {
return balance;
}

// setter:带验证逻辑
public void setBalance(double balance) {
if (balance < 0) {
System.out.println("余额不能为负数!");
return;
}
this.balance = balance;
}

public String getOwner() {
return owner;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testEncapsulation() {
BankAccount account = new BankAccount("小明", 1000);

// account.balance = -999; // 编译报错!private 不让你直接改

account.setBalance(-999); // 输出:余额不能为负数!
System.out.println(account.getBalance()); // 1000(没被改掉)

account.setBalance(2000); // 正常修改
System.out.println(account.getBalance()); // 2000
}

访问修饰符一览

修饰符 本类 同包 子类 其他包
private
(默认/不写)
protected
public

记忆口诀:private 最小,public 最大,protected 给儿子用

继承(Inheritance)

一句话理解:子类自动拥有父类的属性和方法——就像孩子继承了父母的基因,但还能长出自己的独特技能

extends 关键字

Java 用 extends 表示继承关系

语法:class 子类 extends 父类 { }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父类(基类/超类)
public class Animal {
String name;
int age;

public Animal(String name, int age) {
this.name = name;
this.age = age;
}

public void eat() {
System.out.println(name + "在吃东西");
}

public void sleep() {
System.out.println(name + "在睡觉");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子类(派生类)
public class Dog extends Animal {

String breed; // 子类自己的属性:品种

public Dog(String name, int age, String breed) {
super(name, age); // 调用父类构造器
this.breed = breed;
}

// 子类自己的方法
public void fetch() {
System.out.println(name + "在捡球!");
}
}
1
2
3
4
5
6
7
@Test
public void testInheritance() {
Dog dog = new Dog("旺财", 3, "金毛");
dog.eat(); // 继承自父类:旺财在吃东西
dog.sleep(); // 继承自父类:旺财在睡觉
dog.fetch(); // 子类自己的:旺财在捡球!
}

super 关键字

super 指向父类,和 this 指向自己是一对

两个用途:

super(参数) → 调用父类的构造器(必须放在子类构造器的第一行

super.方法名() → 调用父类的方法(当子类重写了同名方法时,想调用原版就用 super)

1
2
3
4
5
6
7
8
9
10
11
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age); // 调用父类 Animal 的构造器
}

@Override
public void eat() {
super.eat(); // 先执行父类的 eat()
System.out.println("(吃的是小鱼干)");
}
}

方法重写(Override)vs 重载(Overload)对比

对比项 重写(Override) 重载(Overload)
发生位置 父子类之间 同一个类
方法名 必须相同 必须相同
参数列表 必须相同 必须不同
返回值 相同或是子类类型 无要求
访问修饰符 子类 ≥ 父类 无要求
关键注解 @Override
目的 子类改写父类行为 提供方法的不同版本

重载的详细内容见 类与对象 中的方法重载章节

@Override 注解

加在重写的方法上面,不是必须的,但强烈建议加

好处:编译器会帮你检查,如果方法签名写错了(比如拼错方法名),立刻报错提醒你

1
2
3
4
@Override
public void eat() {
System.out.println(name + "在优雅地吃猫粮");
}

Java 单继承

Java 只能 extends 一个类(不像 C++ 可以多继承)

为什么?避免”菱形继承”问题——如果爸爸和妈妈都有一个同名方法,儿子到底听谁的?

但 Java 可以 implements 多个接口(后面会学)

Object 是所有类的祖宗

所有类都默认继承 java.lang.Object,即使你没写 extends

所以每个对象都有 toString()equals()hashCode() 等方法——这些都是从 Object 继承来的

1
2
3
4
5
6
7
@Test
public void testObject() {
Dog dog = new Dog("旺财", 3, "金毛");
// toString() 继承自 Object,默认打印地址
System.out.println(dog.toString()); // 类似 Dog@1a2b3c4d
// 后面学了重写 toString(),就能打印有意义的信息了
}

多态(Polymorphism)

一句话理解:同一个方法调用,不同对象有不同表现——你对所有动物说”叫”,狗汪汪叫、猫喵喵叫、鸭子嘎嘎叫

父类引用指向子类对象

这是多态的核心语法:Animal animal = new Dog();

左边是父类类型(Animal),右边是子类对象(Dog)

编译看左边,运行看右边:编译时检查父类有没有这个方法,运行时执行子类的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Animal {
String name;

public Animal(String name) {
this.name = name;
}

public void speak() {
System.out.println(name + "在叫");
}
}

public class Dog extends Animal {
public Dog(String name) {
super(name);
}

@Override
public void speak() {
System.out.println(name + ":汪汪汪!");
}
}

public class Cat extends Animal {
public Cat(String name) {
super(name);
}

@Override
public void speak() {
System.out.println(name + ":喵喵喵~");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testPolymorphism() {
// 父类引用 指向 子类对象
Animal a1 = new Dog("旺财");
Animal a2 = new Cat("咪咪");

a1.speak(); // 旺财:汪汪汪!(运行时执行 Dog 的 speak)
a2.speak(); // 咪咪:喵喵喵~(运行时执行 Cat 的 speak)

// 多态的威力:可以用统一的方式处理不同类型
Animal[] animals = {new Dog("大黄"), new Cat("小白"), new Dog("二哈")};
for (Animal a : animals) {
a.speak(); // 每个动物叫声不同,但代码只写了一行
}
}

向上转型和向下转型

向上转型(自动):子类 → 父类,安全,自动完成

Animal a = new Dog("旺财"); — Dog 自动变成 Animal 类型

向下转型(强制):父类 → 子类,危险,需要手动强转

Dog d = (Dog) a; — 必须确保 a 本来就是 Dog,否则报错 ClassCastException

instanceof 先判断再转,避免翻车

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testCasting() {
Animal animal = new Dog("旺财"); // 向上转型(自动)

// animal.fetch(); // 编译报错!Animal 类型没有 fetch 方法

// 想调用 Dog 特有的方法,需要向下转型
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型(强制)
dog.fetch(); // 旺财在捡球!
}

// 如果类型不对,instanceof 返回 false,安全跳过
if (animal instanceof Cat) {
Cat cat = (Cat) animal; // 不会执行到这里
System.out.println("这是一只猫");
}
}

多态的好处

代码灵活:方法参数用父类类型,就能接收所有子类对象

可扩展:新增子类时,不需要改已有代码

解耦:调用方只依赖父类/接口,不关心具体实现

1
2
3
4
5
6
7
8
// 多态的实际应用:方法参数用父类类型
public class AnimalShop {

// 这个方法可以接收任何 Animal 的子类
public void makeAnimalSpeak(Animal animal) {
animal.speak();
}
}
1
2
3
4
5
6
7
8
@Test
public void testFlexibility() {
AnimalShop shop = new AnimalShop();

shop.makeAnimalSpeak(new Dog("旺财")); // 旺财:汪汪汪!
shop.makeAnimalSpeak(new Cat("咪咪")); // 咪咪:喵喵喵~
// 以后新增一个 Bird 类,这里的代码完全不用改!
}

instanceof 运算符

用来判断一个对象是不是某个类(或其子类)的实例

语法:对象 instanceof 类名,返回 boolean

什么时候用:在向下转型之前,先用 instanceof 检查,避免 ClassCastException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testInstanceof() {
Animal a = new Dog("旺财");

System.out.println(a instanceof Dog); // true(a 本身就是 Dog)
System.out.println(a instanceof Animal); // true(Dog 是 Animal 的子类)
System.out.println(a instanceof Cat); // false(a 不是 Cat)

// Java 16+ 新语法:模式匹配 instanceof(更简洁)
if (a instanceof Dog d) {
// 直接可以用 d,不用再手动强转
d.fetch();
}
}

常见坑汇总

说明 正确做法
字段没有多态 Animal a = new Dog(); a.name 取的是父类的字段 用方法(getter)来访问,方法才有多态
向下转型不检查 直接 (Dog) animal 可能 ClassCastException instanceof 判断
子类构造器忘调 super 如果父类没有无参构造器,子类必须显式 super(参数) 第一行写 super(...)
重写时缩小了访问权限 父类方法是 public,子类改成 private → 编译报错 子类权限 ≥ 父类
以为多态能调子类特有方法 Animal a = new Dog(); a.fetch() 编译报错 先向下转型再调
private 方法能被继承? 不能! private 方法对子类不可见 用 protected 或 public

练习题

练习1(封装):定义一个 User

私有属性:用户名(String)、密码(String)、年龄(int)

setter 中加验证:年龄必须在 0-150 之间,密码长度至少 6 位

写测试方法,尝试设置非法值,验证是否被拦截

练习2(继承 + 多态):设计一个动物类继承体系

父类 Animal:属性 name、方法 speak()move()

子类 Dog:重写 speak() 输出”汪汪汪”,重写 move() 输出”四条腿跑”

子类 Bird:重写 speak() 输出”叽叽喳喳”,重写 move() 输出”展翅飞翔”

子类 Fish:重写 speak() 输出”…(鱼不会叫)”,重写 move() 输出”摇尾游泳”

写一个方法 public void showAnimal(Animal animal),传入不同动物,观察多态效果

练习3(综合思考题):下面的代码输出什么?

1
2
3
4
5
6
Animal a = new Dog("旺财");
System.out.println(a instanceof Animal); // ?
System.out.println(a instanceof Dog); // ?
System.out.println(a instanceof Cat); // ?
a.speak(); // ?
// a.fetch(); // 能编译通过吗?为什么?

学完这篇,下一步可以了解抽象类和接口,那才是多态真正大展身手的地方


上一章 目录 下一章
类与对象 java基础 访问修饰符