类继承(多态+动态绑定)
类继承(多态+动态绑定)
韩顺平 Java P307-P318
继承
以Object类为基类(Base) 向下继承 使用关键字 extends
class Father {}
class Child extends Father {}
子类继承父类所有可公开的属性、方法,规则属性、方法的修饰关键字
访问级别 | 关键字 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | √ | √ | × | × | |
私有 | private | √ | × | × | × |
属性、类的查询原则
在继承的逻辑下,访问属性、方法时,遵循查询原则
从当前类查找同名的属性、方法,如果找到,则访问、使用
md 流程图 flowchat 案例
st=>start: Start
e=>end
init=>operation: 在当前(运行)类查找方法/属性
findInRun=>condition: 当前类中找到了?
findInFather=>condition: 当父类中找到了?
isReadable=>condition: 可访问?
isObject=>condition: 查找到Object?
useIt=>operation: 访问/使用
notUseIt=>operation: 不可访问/使用 抛异常
findGrand=>operation: 继续向上级父类查找
st->init->findInRun
findInRun(yes)->useIt->e
findInRun(no)->findInFather
findInFather(yes, bottom)->isReadable
findInFather(no,right)->findGrand(right)->isObject
isObject(no,top)->findInFather
isObject(yes,bottom)->notUseIt
isReadable(yes,left)->useIt
isReadable(no,bottom)->notUseIt(left)->e
多态
编译类型为父类,运行类型为子类
Father father = new Child child();
// = 为分隔符
// 左侧为编译类型
// 右侧为运行类型
向上转型
-
本质: 父类的引用指向子类的对象
-
语法
父类类型 引用名 = new 子类类型()
-
特点:
-
编译类型看左边,运行类型看右边
-
可以调用父类中的所有成员(遵循访问权限)
-
不能调用子类中的特有成员
-
最终运行效果看子类的具体实现(理解为有重写的使用重写,没有则向上查找)
-
向下转型
-
语法:
子类类型 引用名 = (子类类型) 父类引用
-
只能转换引用,对象不发生改变
-
父类的引用必须指向要转化的对象
-
也即,向上转型中,左右类型互相调换,不可替换为继承树中间的类
-
例如
Father obj = new Child()
-
那么只能转为
Child obj_trans = (Child) obj
-
-
向下转型后,可以调用子类类型中的所有成员
多态案例
类定义
class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
public void eatMouse() {
System.out.println("Cat eatMouse() 小猫吃老鼠...");
}
}
class Dog extends Animal {}
测试用例
public static void main(String[] args) {
/*animal 编译类型就是 Animal , 运行类型 Dog */
Animal animal = new Dog();
/*animal 运行类型是 Dog
遵循查询原则, 从Dog向上逐级查找 cry() 方法
Dog 中没有 cry() 则向上 找到 Animal.cry() */
animal.cry(); // Animal cry() 动物在叫....
animal = new Cat(); //animal 编译类型 Animal,运行类型就是 Cat
animal.cry(); // Cat cry() 小猫喵喵叫...
/* 在编译阶段(写代码时),能调用哪些成员,是由编译类型来决定的 */
animal.eatMouse(); // 报错,无法编译
/* 如果希望在多态中使用子类的方法,则向下转型 */
((Cat)animal).eatMouse;// Cat eatMouse() 小猫吃老鼠...
}
动态绑定
在使用多态时,将使用动态绑定原则,该原则与上文的查询原则不冲突
-
当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定
-
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
总结:
默认使用向上转型 父类类型 引用名 = new 子类类型()
-
对于方法
-
遵循向上转型规则,不可使用子类的特有方法
-
当使用父类子类都有的方法时,可以使用,按子类的重写去执行
-
当子类无此方法时,沿继承树向上查找
-
-
对于属性
-
遵循向上转型规则,不可使用子类的特有属性(重名属性也不使用,但内存中存在)
-
方法所在的类使用所在类的属性值,与编译类型无关
-
案例
class A {
public int i = 10;
//动态绑定机制:
public int sum() {
return getI() + 10;//20(子类有getI 按重写处理) + 10
}
public int sum1() {
return i + 10;//10(获取的是当前类的i = 10) + 10
}
public int sum2() {
return -1;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int sum2() {
return i + 1;
}
public int getI() {
return i; // 获取的是当前类的 i = 20
}
}
umlPlant 类图画法 https://plantuml.com/zh/class-diagram
@startuml
Title "绑定案例"
class A {
+ int i = 10
+ int sum()
+ int sum1()
+ int sum2()
+ int getT()
}
class B extends A {
+ int i = 20
+ int sum2()
+ int getT()
}
@endumlenduml
测试
public static void main(String[] args) {
A obj = new B();
// 方法
int res1 = obj.sum(); // 30
int res2 = obj.sum1(); // 20
int res3 = obj.sum2(); // 21 不是特有方法, 按重写处理
// 属性
int res4 = obj.i; // 10 多态, 不访问子类的独有
int res5 = ((B)obj).i; // 20 想要访问子类的属性/独有方法, 向下转型
}