类继承(多态+动态绑定)

类继承(多态+动态绑定)

韩顺平 Java P307-P318

继承

以Object类为基类(Base) 向下继承 使用关键字 extends

class Father {}
class Child extends Father {}

子类继承父类所有可公开的属性、方法,规则属性、方法的修饰关键字

访问级别 关键字 同类 同包 子类 不同包
公开 public
受保护 protected ×
默认 × ×
私有 private × × ×

属性、类的查询原则

在继承的逻辑下,访问属性、方法时,遵循查询原则

从当前类查找同名的属性、方法,如果找到,则访问、使用

md 流程图 flowchat 案例

http://flowchart.js.org/

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 想要访问子类的属性/独有方法, 向下转型

    }
posted @ 2022-06-24 20:06  jentreywang  阅读(70)  评论(0编辑  收藏  举报