Java面向对象:继承与多态
Java面向对象:继承与多态
1. 引入:为什么需要继承?
1.1 代码重复的问题
假设你要定义两个类:学生和老师。
classStudent{privateStringname;privateintage;privateStringstudentId;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}}classTeacher{privateStringname;privateintage;privateStringteacherId;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}}问题:name和age相关的代码写了两遍,如果以后要加一个字段,两个类都要改。
1.2 继承的解决方案
把公共的部分抽取到父类中:
// 父类classPerson{privateStringname;privateintage;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}}// 子类继承父类classStudentextendsPerson{privateStringstudentId;// 自己独有的属性}classTeacherextendsPerson{privateStringteacherId;// 自己独有的属性}子类自动拥有了父类的属性和方法,代码复用,维护也方便。
2. 继承的基本概念
2.1 继承的格式
class子类extends父类{// 子类自己的成员}2.2 继承的特点
| 特点 | 说明 |
|---|---|
| 单继承 | Java只支持单继承,一个子类只能有一个父类 |
| 多层继承 | 可以有多层,如A→B→C,C继承B的,B继承A的 |
| 所有类都继承Object | 如果没写extends,默认继承Object类 |
2.3 继承的优点和缺点
优点:
- 代码复用,减少重复
- 便于维护(改父类,子类自动生效)
- 为多态打下基础
缺点:
- 父子类耦合度高(父类改了,子类可能受影响)
- 破坏了封装(子类可以访问父类的protected成员)
3. 继承中成员变量的访问
3.1 不同名变量
子类和父类变量名不同,直接访问就行:
classFather{intmoney=100;}classSonextendsFather{intcar=1;voidshow(){System.out.println(money);// 100,继承来的System.out.println(car);// 1,自己的}}3.2 同名变量(就近原则)
子类和父类变量名相同时,优先用子类的:
classFather{intscore=80;}classSonextendsFather{intscore=95;voidshow(){intscore=100;System.out.println(score);// 100(局部变量)System.out.println(this.score);// 95(子类成员)System.out.println(super.score);// 80(父类成员)}}3.3 super关键字
super用来访问父类的成员:
| 用法 | 说明 |
|---|---|
super.成员变量 | 访问父类的成员变量 |
super.成员方法 | 访问父类的成员方法 |
super() | 调用父类的构造方法 |
4. 继承中成员方法的访问
4.1 方法重写(Override)
子类可以对父类的方法进行重写,提供自己的实现:
classAnimal{publicvoidspeak(){System.out.println("动物叫");}}classCatextendsAnimal{@Overridepublicvoidspeak(){System.out.println("喵喵喵");}}4.2 重写的条件
| 条件 | 要求 |
|---|---|
| 方法名 | 必须相同 |
| 参数列表 | 必须相同 |
| 返回值类型 | 必须相同或者是其子类 |
| 访问权限 | 不能低于父类(父类public,子类不能private) |
| 异常 | 不能抛出比父类更大的异常 |
4.3 @Override注解
推荐在重写的方法上加@Override注解:
- 让编译器帮忙检查是否真的重写了
- 提高代码可读性
4.4 重写 vs 重载
| 维度 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 发生位置 | 父子类之间 | 同一个类中 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须相同 | 必须不同 |
| 返回值 | 相同或其子类 | 无关 |
| 注解 | @Override | 无 |
5. 继承中构造方法
5.1 父子类构造方法的执行顺序
子类构造方法默认第一行会调用父类的无参构造:
classFather{publicFather(){System.out.println("父类构造方法");}}classSonextendsFather{publicSon(){super();// 默认就有,可以省略System.out.println("子类构造方法");}}// 创建对象Sons=newSon();// 输出:// 父类构造方法// 子类构造方法5.2 父类没有无参构造怎么办?
如果父类只写了有参构造,没有无参构造,子类必须手动用super(参数)调用:
classFather{privateStringname;publicFather(Stringname){// 只有有参构造this.name=name;}}classSonextendsFather{publicSon(Stringname){super(name);// 必须手动调用父类的有参构造}}5.3 super()的规则
| 规则 | 说明 |
|---|---|
| 必须写在第一行 | super()必须是构造方法的第一条语句 |
| 和this()互斥 | 一个构造方法不能同时调用super()和this() |
| 不写默认有 | 如果不写,编译器会自动加super() |
6. 多态
6.1 什么是多态?
同一种行为,不同对象表现出不同形态。
比如“吃”这个行为:
- 猫吃鱼
- 狗吃肉
- 人吃饭
6.2 多态的条件
| 条件 | 说明 |
|---|---|
| 继承 | 有父子类关系 |
| 重写 | 子类重写了父类的方法 |
| 父类引用指向子类对象 | Father f = new Son(); |
6.3 多态的代码体现
classAnimal{publicvoideat(){System.out.println("动物吃东西");}}classCatextendsAnimal{@Overridepublicvoideat(){System.out.println("猫吃鱼");}}classDogextendsAnimal{@Overridepublicvoideat(){System.out.println("狗吃肉");}}publicclassTest{publicstaticvoidmain(String[]args){// 多态:父类引用指向子类对象Animala1=newCat();Animala2=newDog();a1.eat();// 猫吃鱼a2.eat();// 狗吃肉}}6.4 多态的成员访问特点
| 成员类型 | 访问特点 |
|---|---|
| 成员变量 | 编译看左边,运行看左边(用父类的) |
| 成员方法 | 编译看左边,运行看右边(用子类的) |
| 静态方法 | 编译看左边,运行看左边(不算重写) |
classFather{intnum=10;publicvoidshow(){System.out.println("Father show");}publicstaticvoidtest(){System.out.println("Father static");}}classSonextendsFather{intnum=20;@Overridepublicvoidshow(){System.out.println("Son show");}publicstaticvoidtest(){System.out.println("Son static");}}// 使用Fatherf=newSon();System.out.println(f.num);// 10(父类的变量)f.show();// Son show(子类的方法)f.test();// Father static(静态方法看左边)7. 多态的优缺点
7.1 优点
解耦:方法参数写成父类类型,可以接收任何子类对象:
// 不用多态:每种动物都要写一个方法publicvoidfeedCat(Catcat){cat.eat();}publicvoidfeedDog(Dogdog){dog.eat();}publicvoidfeedPig(Pigpig){pig.eat();}// 用多态:一个方法搞定所有publicvoidfeed(Animalanimal){animal.eat();}扩展性:新增动物类,不需要修改已有代码。
7.2 缺点
不能直接调用子类特有方法:
Animala=newCat();a.eat();// 父类有,可以a.catchMouse();// 父类没有,不能调用解决方法:向下转型
if(ainstanceofCat){Catc=(Cat)a;c.catchMouse();// 转成Cat后可以调用}8. 向上转型和向下转型
8.1 向上转型(自动)
Catcat=newCat();Animalanimal=cat;// 向上转型,自动完成特点:父类引用指向子类对象,但只能调用父类已有的方法。
8.2 向下转型(强制)
Animalanimal=newCat();Catcat=(Cat)animal;// 向下转型,需要强制转换8.3 instanceof关键字
向下转型前,建议先用instanceof判断类型:
Animalanimal=newCat();if(animalinstanceofCat){Catcat=(Cat)animal;cat.catchMouse();}elseif(animalinstanceofDog){Dogdog=(Dog)animal;dog.watchHouse();}Java 16+ 新写法:
if(animalinstanceofCatcat){cat.catchMouse();// 自动转换,直接用}9. 综合总结
9.1 继承总结
| 维度 | 要点 |
|---|---|
| 格式 | class 子类 extends 父类 |
| 单继承 | 一个子类只能有一个父类 |
| 成员变量 | 就近原则,用super访问父类 |
| 成员方法 | 可以重写,用@Override注解 |
| 构造方法 | 先调用父类构造,再调子类 |
9.2 多态总结
| 维度 | 要点 |
|---|---|
| 条件 | 继承 + 重写 + 父类引用指向子类对象 |
| 变量 | 编译运行都看左边(父类) |
| 方法 | 编译看左边,运行看右边(子类重写的) |
| 静态 | 编译运行都看左边 |
| 转型 | 向上自动,向下强制(建议先instanceof) |
9.3 对比表格
| 对比项 | 继承 | 多态 |
|---|---|---|
| 核心作用 | 代码复用 | 灵活扩展 |
| 关键词 | extends | 父类引用指向子类对象 |
| 方法调用 | 调用哪个看对象类型 | 编译看左,运行看右 |
| 典型场景 | 抽取公共代码 | 方法参数写成父类类型 |
10. 易错点总结
- 把重写写成重载:参数列表不同,导致不是重写而是新方法
- 子类构造方法忘记调用super:父类没有无参构造时必须手动调用
- this和super混淆:this访问当前类,super访问父类
- 向下转型不判断类型:可能导致
ClassCastException
