|
@@ -404,6 +404,210 @@ public class Bird extends Animal {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+### 13. 继承中变量的访问特点——就近原则
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Demo02.java`、`Son.java`
|
|
|
|
|
+
|
|
|
|
|
+在子类中访问一个变量时,采用 **就近原则** 查找:
|
|
|
|
|
+
|
|
|
|
|
+| 查找顺序 | 查找范围 | 示例(Son 中访问 `a`) |
|
|
|
|
|
+|:--------:|---------|----------------------|
|
|
|
|
|
+| 1️⃣ | 子类的 **局部范围**(方法内部) | `int a = 10;`(局部变量) |
|
|
|
|
|
+| 2️⃣ | 子类的 **成员范围**(成员变量) | `int a;`(子类成员变量) |
|
|
|
|
|
+| 3️⃣ | **父类** 的成员范围 | `int c;`(Dad 的成员变量) |
|
|
|
|
|
+| 4️⃣ | **父类的父类**……直到 Object | `int d;`(Granddad 的成员变量) |
|
|
|
|
|
+| ❌ | 如果都没找到 | 编译报错 |
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Son.java —— 演示就近原则
|
|
|
|
|
+public class Son extends Dad {
|
|
|
|
|
+ int a; // 子类的成员变量(第2层)
|
|
|
|
|
+ int b; // 子类的成员变量(第2层)
|
|
|
|
|
+
|
|
|
|
|
+ public void eat() {
|
|
|
|
|
+ int a = 10; // 局部变量(第1层)
|
|
|
|
|
+ super.eat();
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println(a); // 10 —— 局部范围找到 a
|
|
|
|
|
+ System.out.println(b); // 0 —— 局部无 b → 子类成员找到 b
|
|
|
|
|
+ System.out.println(c); // 0 —— 子类无 c → 父类 Dad 找到 c
|
|
|
|
|
+ System.out.println(d); // 0 —— 父类无 d → 祖父 Granddad 找到 d
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Granddad.java —— 顶层父类定义了 d
|
|
|
|
|
+public class Granddad {
|
|
|
|
|
+ int d;
|
|
|
|
|
+ public void eat() {
|
|
|
|
|
+ System.out.println("爷爷爱喝酒....");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Dad.java —— 父类定义了 c
|
|
|
|
|
+public class Dad extends Granddad {
|
|
|
|
|
+ int c;
|
|
|
|
|
+ public void eat() {
|
|
|
|
|
+ super.eat();
|
|
|
|
|
+ System.out.println("爸爸爱吃肉....");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **总结**:变量的查找路径为 **局部 → 本类成员 → 父类成员 → 祖父类成员 → … → Object**。如果一直找到 Object 都没有,则编译报错。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 14. this vs super 完整对比
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Demo02.java`
|
|
|
|
|
+
|
|
|
|
|
+`this` 和 `super` 是 Java 中两个非常重要的关键字:
|
|
|
|
|
+
|
|
|
|
|
+| 关键字 | 含义 | 示例 |
|
|
|
|
|
+|--------|------|------|
|
|
|
|
|
+| `this` | 代表 **本类** 对象的引用 | `this.name`、`this.eat()`、`this()` |
|
|
|
|
|
+| `super` | 代表 **父类** 对象的引用 | `super.name`、`super.eat()`、`super()` |
|
|
|
|
|
+
|
|
|
|
|
+**在三种场景中的使用对比**:
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | `this` 用法 | `super` 用法 |
|
|
|
|
|
+|------|------------|-------------|
|
|
|
|
|
+| **成员变量** | `this.成员变量` —— 访问本类的成员变量 | `super.成员变量` —— 访问父类的成员变量 |
|
|
|
|
|
+| **成员方法** | `this.成员方法()` —— 调用本类的成员方法 | `super.成员方法()` —— 调用父类的成员方法 |
|
|
|
|
|
+| **构造方法** | `this()` / `this(参数)` —— 调用本类的其他构造方法 | `super()` / `super(参数)` —— 调用父类的构造方法 |
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Demo02.java(注释中总结)
|
|
|
|
|
+/*
|
|
|
|
|
+this 代表本类对象的引用
|
|
|
|
|
+super 代表父类对象的引用
|
|
|
|
|
+
|
|
|
|
|
+成员变量中:
|
|
|
|
|
+ this.成员变量 --> 访问本类的成员变量
|
|
|
|
|
+ super.成员变量 --> 访问父类的成员变量
|
|
|
|
|
+成员方法中:
|
|
|
|
|
+ this.成员方法 --> 调用本类的成员方法
|
|
|
|
|
+ super.成员方法 --> 调用父类的成员方法
|
|
|
|
|
+构造方法中:
|
|
|
|
|
+ this() --> 访问本类的无参构造方法
|
|
|
|
|
+ this(参数) --> 访问本类的有参构造方法
|
|
|
|
|
+ super() --> 访问父类的无参构造方法
|
|
|
|
|
+ super(参数) --> 访问父类的有参构造方法
|
|
|
|
|
+*/
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **注意**:`this()` 和 `super()` 都必须写在构造方法的 **第一行**,且不能同时出现。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 15. 继承中构造方法的特点
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Demo02.java`、`Animal.java`、`Dog.java`、`Test03.java`、`Fu.java`、`Zi.java`、`Person.java`、`Student.java`
|
|
|
|
|
+
|
|
|
|
|
+#### 15.1 子类构造方法默认调用父类无参构造
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Animal.java`、`Dog.java`、`Test03.java`
|
|
|
|
|
+
|
|
|
|
|
+子类的所有构造方法(有参或无参)都会 **默认调用父类的无参构造方法**。每个子类构造方法的 **第一句** 默认都是 `super()`。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Animal.java —— 父类:添加无参构造方法打印信息
|
|
|
|
|
+public class Animal {
|
|
|
|
|
+ public Animal() {
|
|
|
|
|
+ System.out.println("动物类的无参构造方法");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Dog.java —— 子类:没有写任何构造方法,默认有无参构造
|
|
|
|
|
+public class Dog extends Animal {
|
|
|
|
|
+ // 隐藏的默认构造方法:
|
|
|
|
|
+ // public Dog() {
|
|
|
|
|
+ // super(); // 自动调用父类的无参构造
|
|
|
|
|
+ // }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Test03.java —— 测试
|
|
|
|
|
+public class Test03 {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ Dog d = new Dog();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**输出结果**:
|
|
|
|
|
+```
|
|
|
|
|
+动物类的无参构造方法
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **原理**:子类在初始化(创建对象)之前,一定要先完成对父类对象的创建。因为子类会继承父类的数据,可能还会使用父类的数据,所以必须先创建父类对象。这就是为什么每个子类构造方法的第一句默认都是 `super()`。
|
|
|
|
|
+
|
|
|
|
|
+#### 15.2 父类没有无参构造时的处理
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Fu.java`、`Zi.java`、`Person.java`、`Student.java`
|
|
|
|
|
+
|
|
|
|
|
+**情况一**:父类 **没有** 无参构造方法(只有有参构造),子类必须手动调用父类的有参构造。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Fu.java —— 父类:只有有参构造
|
|
|
|
|
+public class Fu {
|
|
|
|
|
+ private String name;
|
|
|
|
|
+
|
|
|
|
|
+ public Fu(String name) { // 有参构造覆盖了默认无参构造
|
|
|
|
|
+ this.name = name;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Zi.java —— 子类:必须手动调用父类的有参构造
|
|
|
|
|
+public class Zi extends Fu {
|
|
|
|
|
+ public Zi() {
|
|
|
|
|
+ super("默认名"); // ❗ 必须手动调用父类的有参构造,否则编译报错
|
|
|
|
|
+ System.out.println("子类构造方法");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**情况二**:父类 **有无参构造但被 private 修饰**,子类也无法调用。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Fu.java —— 父类有无参构造但是 private
|
|
|
|
|
+// private Fu() { } // private 构造方法,子类不可见
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 15.3 综合示例:Person → Student
|
|
|
|
|
+
|
|
|
|
|
+**文件**:`Person.java`、`Student.java`
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// Person.java —— 父类只有有参构造
|
|
|
|
|
+public class Person {
|
|
|
|
|
+ private String name;
|
|
|
|
|
+ private Integer age;
|
|
|
|
|
+
|
|
|
|
|
+ public Person(String name, Integer age) {
|
|
|
|
|
+ this.name = name;
|
|
|
|
|
+ this.age = age;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Student.java —— 子类必须手动调用 super(name, age)
|
|
|
|
|
+public class Student extends Person {
|
|
|
|
|
+ private String school;
|
|
|
|
|
+
|
|
|
|
|
+ public Student(String name, Integer age, String school) {
|
|
|
|
|
+ super(name, age); // 调用父类的有参构造,传入参数
|
|
|
|
|
+ this.school = school;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **核心规则**:
|
|
|
|
|
+> 1. 子类构造方法 **默认第一句** 是 `super()`(调用父类无参构造)
|
|
|
|
|
+> 2. 如果父类 **没有无参构造**,子类必须通过 `super(参数)` 指定调用父类的有参构造
|
|
|
|
|
+> 3. 如果父类 **有无参构造但是 private**,子类同样无法调用,也需要指定其他构造
|
|
|
|
|
+> 4. `super(参数)` 必须在子类构造方法的 **第一行**
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
**包名**:`exercise.studentSystem`
|
|
**包名**:`exercise.studentSystem`
|
|
|
|
|
|
|
|
### 题目要求
|
|
### 题目要求
|
|
@@ -688,6 +892,10 @@ public class BookManagerTest {
|
|
|
| **@Override 注解** | ⭐⭐⭐⭐ | 可选的检查注解,推荐加上帮助编译器校验 |
|
|
| **@Override 注解** | ⭐⭐⭐⭐ | 可选的检查注解,推荐加上帮助编译器校验 |
|
|
|
| **super 关键字** | ⭐⭐⭐⭐⭐ | `super.方法()` 调用父类方法,`super.属性` 访问父类属性 |
|
|
| **super 关键字** | ⭐⭐⭐⭐⭐ | `super.方法()` 调用父类方法,`super.属性` 访问父类属性 |
|
|
|
| **同名属性处理** | ⭐⭐⭐⭐ | 父子类同名属性同时存在,`this` 访问自己的,`super` 访问父类的 |
|
|
| **同名属性处理** | ⭐⭐⭐⭐ | 父子类同名属性同时存在,`this` 访问自己的,`super` 访问父类的 |
|
|
|
|
|
+| **变量的就近原则** | ⭐⭐⭐⭐⭐ | 变量查找路径:局部 → 本类成员 → 父类成员 → 祖父类成员 → Object |
|
|
|
|
|
+| **this vs super 对比** | ⭐⭐⭐⭐ | `this`=本类引用,`super`=父类引用,在变量/方法/构造方法三场景对比 |
|
|
|
|
|
+| **构造方法继承特点** | ⭐⭐⭐⭐⭐ | 子类构造方法默认第一句 `super()` 调用父类无参构造 |
|
|
|
|
|
+| **父类无无参构造处理** | ⭐⭐⭐⭐⭐ | 父类无无参构造时,子类必须手动 `super(参数)` 指定有参构造 |
|
|
|
| **单继承** | ⭐⭐⭐⭐⭐ | Java 一个子类只能有一个直接父类 |
|
|
| **单继承** | ⭐⭐⭐⭐⭐ | Java 一个子类只能有一个直接父类 |
|
|
|
| **多层继承** | ⭐⭐⭐⭐ | 支持链式继承(Son → Dad → Granddad) |
|
|
| **多层继承** | ⭐⭐⭐⭐ | 支持链式继承(Son → Dad → Granddad) |
|
|
|
| **Object 根类** | ⭐⭐⭐⭐ | Java 所有类都隐式继承 Object |
|
|
| **Object 根类** | ⭐⭐⭐⭐ | Java 所有类都隐式继承 Object |
|
|
@@ -700,11 +908,14 @@ public class BookManagerTest {
|
|
|
2. **多层继承 ≠ 多继承**:Java 支持 Son → Dad → Granddad 这样的链式继承,但不支持同时继承多个类
|
|
2. **多层继承 ≠ 多继承**:Java 支持 Son → Dad → Granddad 这样的链式继承,但不支持同时继承多个类
|
|
|
3. **私有成员不可访问**:子类不能直接使用父类 `private` 修饰的成员
|
|
3. **私有成员不可访问**:子类不能直接使用父类 `private` 修饰的成员
|
|
|
4. **构造方法不可继承**:父类的构造方法不会被子类继承,子类通过 `super(参数)` 调用父类构造方法
|
|
4. **构造方法不可继承**:父类的构造方法不会被子类继承,子类通过 `super(参数)` 调用父类构造方法
|
|
|
-5. **方法重写 vs 方法重载**:
|
|
|
|
|
|
|
+5. **子类构造方法默认调用 `super()`**:每个子类构造方法的第一句默认都是 `super()`,如果父类没有无参构造,必须手动 `super(参数)`
|
|
|
|
|
+6. **方法重写 vs 方法重载**:
|
|
|
- 重写(Override):父子类之间,方法签名完全一致
|
|
- 重写(Override):父子类之间,方法签名完全一致
|
|
|
- 重载(Overload):同一个类中,方法名相同参数列表不同
|
|
- 重载(Overload):同一个类中,方法名相同参数列表不同
|
|
|
-6. **`@Override` 注解**:不强制但建议加上,可以帮助编译器检查是否真正重写了父类方法
|
|
|
|
|
-7. **继承的耦合性**:合理设计继承层次,避免过深的继承链,优先考虑组合而非继承
|
|
|
|
|
|
|
+7. **`@Override` 注解**:不强制但建议加上,可以帮助编译器检查是否真正重写了父类方法
|
|
|
|
|
+8. **变量访问就近原则**:局部 → 本类成员 → 父类成员 → 祖父类成员 → …… → Object
|
|
|
|
|
+9. **`super()` 和 `this()` 不能同时出现**:都必须写在构造方法的第一行
|
|
|
|
|
+10. **继承的耦合性**:合理设计继承层次,避免过深的继承链,优先考虑组合而非继承
|
|
|
|
|
|
|
|
### 拓展阅读
|
|
### 拓展阅读
|
|
|
|
|
|