Java 面向对象基础(二)
作者:没有四次元口袋的蓝胖
日期:2026-06-04
标签:Java, 面向对象, 继承, super, 重写, 多态
一、继承
1.1 什么是继承?
继承是子类复用父类属性和行为的机制,体现“is-a”关系。
classAnimal{Stringname;publicvoideat(){System.out.println(name+"在吃东西");}}classDogextendsAnimal{publicvoidbark(){System.out.println(name+"在汪汪叫");}}Dogd=newDog();d.name="旺财";d.eat();// 继承自 Animald.bark();// Dog 自己的1.2 继承的特点
| 特点 | 说明 |
|---|---|
| 单继承 | 一个类只能有一个直接父类(Java 不支持多继承) |
| 多层继承 | A → B → C,可以形成继承链 |
| 所有类根 | 没有显式继承时,默认继承java.lang.Object |
| 子类获得 | 父类非 private 的属性和方法 |
| 子类不可获 | 父类的构造方法、private 成员 |
1.3 继承中的访问权限
子类能访问的父类成员: ✅ public → 哪里都能用 ✅ protected → 同包 + 不同包子类 ✅ 缺省(default) → 同包 ❌ private → 只有父类自己能用(但可以通过 public/protected 的 getter 间接访问)1.4 什么时候用继承?
is-a 关系才用继承,has-a 关系用组合。
✅ Dog is an Animal → 继承(狗是动物) ✅ Cat is an Animal → 继承(猫是动物) ❌ Car has a Wheel → 组合,不是继承(车有轮胎) ❌ Student has a Book → 组合,不是继承(学生有书)你可以说 “狗是动物”、“猫是动物”,但是不能说“车是轮胎”“学生是书”,不然就会出现“给汽车充气”、“获取学生价格”这样不合逻辑的情况。
二、super 关键字
2.1 super 是什么?
super代表父类的引用,用于在子类中访问父类的成员。
2.2 三种用法
用法 1:访问父类成员变量
当子类和父类有同名变量时,用super区分:
classAnimal{Stringname="动物";}classDogextendsAnimal{Stringname="狗";publicvoidshow(){System.out.println(name);// 狗(子类)System.out.println(super.name);// 动物(父类)}}用法 2:调用父类成员方法
classAnimal{publicvoideat(){System.out.println("动物在吃东西");}}classDogextendsAnimal{@Overridepublicvoideat(){super.eat();// 先调用父类版本System.out.println("狗在啃骨头");}}newDog().eat();// 动物在吃东西// 狗在啃骨头用法 3:调用父类构造方法
classAnimal{Stringname;publicAnimal(Stringname){this.name=name;}}classDogextendsAnimal{Stringbreed;publicDog(Stringname,Stringbreed){super(name);// 调用父类构造,必须放在第一行this.breed=breed;}}2.3 super vs this
| 对比 | super | this |
|---|---|---|
| 代表 | 父类引用 | 当前对象引用 |
| 访问成员 | super.变量/super.方法() | this.变量/this.方法() |
| 调用构造 | super(...)在子类构造第一行 | this(...)在构造第一行 |
| 互斥 | super()和this()不能同时出现在一个构造方法中 | 同左 |
2.4 子类构造的隐式 super()
classAnimal{publicAnimal(){System.out.println("Animal 构造");}}classDogextendsAnimal{publicDog(){super();// 即使不写,编译器也会自动加上System.out.println("Dog 构造");}}newDog();// Animal 构造// Dog 构造⚠️ 如果父类没有无参构造,子类必须显式用
super(...)调用父类有参构造,否则编译报错。
三、方法重写(Override)
3.1 什么是重写?
子类对父类的方法重新实现,使同一方法在不同子类中表现不同行为。
3.2 重写规则
| 规则 | 说明 |
|---|---|
| 方法名 | 必须相同 |
| 参数列表 | 必须相同 |
| 返回值类型 | 相同,或是父类返回值类型的子类(协变返回) |
| 访问权限 | 不能比父类更严格(可以更宽) |
| 异常 | 不能抛出比父类更多的受检异常 |
private/static/final | 不能被重写 |
权限放宽方向:private→ 缺省 →protected→public
classAnimal{publicObjectgetInfo(){return"动物信息";}}classDogextendsAnimal{@OverridepublicStringgetInfo(){return"狗的信息";}// ✅ String 是 Object 的子类(协变返回)}3.3 @Override 注解
强烈建议每次重写都加上@Override,让编译器帮你检查:
classDogextendsAnimal{@Overridepublicvoideat(){}// ✅ 正确重写@Overridepublicvoideet(){}// ❌ 编译报错,父类没有这个方法,说明你拼错了}3.4 重写 vs 重载(完整对比)
| 对比 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 发生位置 | 同一个类 | 子类与父类 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回值 | 无关 | 相同或子类 |
| 访问权限 | 无关 | 不能更严格 |
| 关系 | 编译时多态 | 运行时多态 |
四、多态
4.1 什么是多态?
同一个引用类型,调用同一个方法,表现出不同的行为。
核心前提:
- 有继承 / 实现关系
- 有方法重写
- 父类引用指向子类对象
4.2 基本用法
classAnimal{publicvoidspeak(){System.out.println("动物叫");}}classDogextendsAnimal{@Overridepublicvoidspeak(){System.out.println("汪汪汪");}}classCatextendsAnimal{@Overridepublicvoidspeak(){System.out.println("喵喵喵");}}Animala1=newDog();// 父类引用 → 子类对象(向上转型)Animala2=newCat();a1.speak();// 汪汪汪 ← 运行时看实际对象类型a2.speak();// 喵喵喵 ← 运行时看实际对象类型编译看左边,运行看右边——编译时检查引用类型有没有这个方法,运行时执行实际对象的重写版本。
4.3 向上转型与向下转型
向上转型(自动,安全)
Animala=newDog();// 子类 → 父类,自动转型a.speak();// 汪汪汪(多态生效)// a.bark(); // ❌ 编译报错,Animal 没有 bark()向上转型后,只能调用父类中声明的方法,不能调用子类特有的方法。
向下转型(强制,需谨慎)
Animala=newDog();Dogd=(Dog)a;// 强制向下转型d.bark();// ✅ 现在可以调用了Catc=(Cat)a;// ❌ 运行时 ClassCastException!a 实际是 Doginstanceof 判断(安全转型的保障)
Animala=newDog();if(ainstanceofDog){Dogd=(Dog)a;d.bark();}elseif(ainstanceofCat){Catc=(Cat)a;c.catchMouse();}4.4 多态的实际应用
场景:方法参数用父类类型,兼容所有子类
classOwner{publicvoidfeed(Animalanimal){// 任何 Animal 子类都能传animal.speak();System.out.println("喂食完成");}}Ownerowner=newOwner();owner.feed(newDog());// 汪汪汪 → 喂食完成owner.feed(newCat());// 喵喵喵 → 喂食完成不用多态的话,你要写
feed(Dog d)、feed(Cat c)……每加一种动物就加一个方法。多态一个方法搞定。
4.5 多态中成员变量没有多态效果
classAnimal{Stringname="动物";}classDogextendsAnimal{Stringname="狗";}Animala=newDog();System.out.println(a.name);// 动物 ← 成员变量看左边(引用类型)// 多态只对方法有效,变量没有多态五、常见坑与面试考点
坑 1:父类没有无参构造,子类编译报错
classAnimal{publicAnimal(Stringname){}// 只有有参构造,无参构造没了}classDogextendsAnimal{publicDog(){}// ❌ 编译报错!隐式 super() 找不到无参构造publicDog(){super("默认");}// ✅ 显式调用父类有参构造}坑 2:重写时缩小了访问权限
classAnimal{publicvoideat(){}}classDogextendsAnimal{@Overridevoideat(){}// ❌ 从 public 缩小到 default,编译报错}坑 3:类初始化顺序(经典面试题)
classParent{static{System.out.println("1. 父类静态代码块");}{System.out.println("2. 父类实例代码块");}publicParent(){System.out.println("3. 父类构造方法");}}classChildextendsParent{static{System.out.println("4. 子类静态代码块");}{System.out.println("5. 子类实例代码块");}publicChild(){System.out.println("6. 子类构造方法");}}newChild();// 输出顺序:1 → 4 → 2 → 3 → 5 → 6口诀:父类静态 → 子类静态 → 父类实例/构造 → 子类实例/构造
六、思维导图速览
面向对象进阶(二)—— 继承与多态 ├── 继承 │ ├── is-a 关系,单继承 │ ├── 子类获得父类非 private 成员 │ └── 所有类默认继承 Object ├── super 关键字 │ ├── 访问父类成员(super.变量 / super.方法()) │ ├── 调用父类构造(super(),必须在第一行) │ └── vs this 对比 ├── 方法重写(Override) │ ├── 规则:方法名/参数相同,权限不能更严 │ ├── @Override 注解(强烈建议) │ └── vs 重载(Overload)完整对比 └── 多态 ├── 编译看左边,运行看右边 ├── 向上转型(自动)/ 向下转型(强制 + instanceof) ├── 实际应用:方法参数用父类类型 └── 成员变量没有多态效果写在最后
- 多态是重中之重——理解"编译看左、运行看右"是打开 Java 面向对象大门的钥匙,结合内存模型想几遍就通了。
- super 三种用法要熟练,尤其是
super()调用父类构造。 - 重写 vs 重载现在就分清楚,后面学泛型会更晕,趁早搞明白。
- 初始化顺序口诀记住,面试手写一遍验证。