• 36_多态


    第36章 多态

    作者:张子默

    一、概述

    1、引入

    多态是继封装、继承之后,面向对象的第三大特性。

    生活中,比如跑的动作,小猫,小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

    2、定义

    多态是指同一行为,具有多个不同的表现形式。

    3、前提【重点】

    • 继承或者实现【二选一】
    • 方法的重写【意义体现:不重写,无意义】
    • 父类引用指向子类对象【格式体现】

    二、多态的体现

    多态体现的格式:

    父类类型 变量名 = new 子类对象;
    变量名.方法名();
    
    • 1
    • 2

    父类类型:指子类对象继承的父类类型,或者实现的父类接口类型。

    代码如下:

    Fu f = new Zi();
    f.method();
    
    • 1
    • 2

    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后的方法。

    定义父类:

    public abstract class Animal {
    	public abstract void eat();
    }
    
    • 1
    • 2
    • 3

    定义子类:

    class Cat extends Animal {
        public void eat() {
        	System.out.println("吃鱼");
        }
    }
    class Dog extends Animal {
        public void eat() {
        	System.out.println("吃骨头");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义测试类:

    public class Test {
        public static void main(String[] args) {
            // 多态形式,创建对象
            Animal a1 = new Cat();
            // 调用的是 Cat 的 eat
            a1.eat();
            // 多态形式,创建对象
            Animal a2 = new Dog();
            // 调用的是 Dog 的 eat
            a2.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、多态的好处

    实际开发的过程中,父类类型作为方法的形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。代码如下:

    定义父类:

    public abstract class Animal {
    	public abstract void eat();
    }
    
    • 1
    • 2
    • 3

    定义子类:

    class Cat extends Animal {
        public void eat() {
        	System.out.println("吃鱼");
        }
    }
    class Dog extends Animal {
        public void eat() {
        	System.out.println("吃骨头");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义测试类:

    public class Test {
        public static void main(String[] args) {
            // 多态形式,创建对象
            Cat c = new Cat();
            Dog d = new Dog();
            // 调用showCatEat
            showCatEat(c);
            // 调用showDogEat
            showDogEat(d);
            /*
            以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
            而执行效果一致
            */
            showAnimalEat(c);
            showAnimalEat(d);
        }
        public static void showCatEat (Cat c){
        	c.eat();
        }
        public static void showDogEat (Dog d){
        	d.eat();
        }
        public static void showAnimalEat (Animal a){
        	a.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法。

    当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法。

    不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成。

    所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

    四、引用类型转换

    多态的转型分为向上转型与向下转型两种:

    1、向上转型

    向上转型的过程是默认的,多态本身是子类类型向父类类型向上转型的过程。

    当父类引用指向一个子类对象时,便是向上转型。

    使用格式:

    父类类型 变量名 = new 子类类型();
    如:Animal a = new Cat();
    
    • 1
    • 2

    2、向下转型

    向下转型的过程是强制的,父类类型向子类类型向下转型的过程。

    一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

    使用格式:

    子类类型 变量名 = (子类类型) 父类变量名;:Cat c =(Cat) a;
    
    • 1
    • 2

    3、转型的缘由

    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

    代码类:

    abstract class Animal {
    	abstract void eat();
    }
    class Cat extends Animal {
        public void eat() {
        	System.out.println("吃鱼");
        }
        public void catchMouse() {
        	System.out.println("抓老鼠");
        }
    }
    class Dog extends Animal {
        public void eat() {
        	System.out.println("吃骨头");
        }
        public void watchHouse() {
        	System.out.println("看家");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    定义测试类:

    public class Test {
        public static void main(String[] args) {
            // 向上转型
            Animal a = new Cat();
            a.eat(); // 调用的是 Cat 的 eat
            // 向下转型
            Cat c = (Cat)a;
            c.catchMouse(); // 调用的是 Cat 的 catchMouse
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4、转型的异常

    转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

    public class Test {
        public static void main(String[] args) {
            // 向上转型
            Animal a = new Cat();
            a.eat(); // 调用的是 Cat 的 eat
            // 向下转型
            Dog d = (Dog)a;
            d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这段代码可以通过编译,但是运行时,却报出了ClassCastException,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

    为了避免ClassCastException的发生,Java提供了instanceof关键字,给引用变量做类型的检验,格式如下:

    变量名 instanceof 数据类型
    如果变量属于该数据类型,返回true。
    如果变量不属于该数据类型,返回false
    • 1
    • 2
    • 3

    所以,转换前,我们最好先做一个判断,代码如下:

    public class Test {
        public static void main(String[] args) {
            // 向上转型
            Animal a = new Cat();
            a.eat(); // 调用的是 Cat 的 eat
            // 向下转型
            if (a instanceof Cat){
                Cat c = (Cat)a;
                c.catchMouse(); // 调用的是 Cat 的 catchMouse
            } else if (a instanceof Dog){
                Dog d = (Dog)a;
                d.watchHouse(); // 调用的是 Dog 的 watchHouse
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    五、实例

    • Java的多态性
    package com.zzm.day10.demo04;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:39
     * 创建人:张子默
     */
    public class Fu {
    
        public void method() {
            System.out.println("父类方法");
        }
    
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    
    }
    
    package com.zzm.day10.demo04;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:39
     * 创建人:张子默
     */
    public class Zi extends Fu {
    
        @Override
        public void method() {
            System.out.println("子类方法");
        }
    }
    
    package com.zzm.day10.demo04;
    
    /**
     * 用途:
     * 时间:2021/6/22 17:54
     * 创建人:张子默
     */
    
    /*
    代码当中体现多态性,其实就是一句话,父类引用指向子类对象。
    格式:
        父类名称 对象名 = new 子类名称();
    或者:
        接口名称 对象名 = new 实现类名称();
     */
    public class Demo01Multi {
    
        public static void main(String[] args) {
            // 使用多态的方法
            // 左侧父类的引用,指向了右侧子类的对象
            Fu obj = new Zi();
    
            obj.method();
            obj.methodFu();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 多态的成员访问
    package com.zzm.day10.demo05;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:44
     * 创建人:张子默
     */
    public class Fu {
    
        int num = 10;
    
        public void showNum() {
            System.out.println(num);
        }
    
        public void method() {
            System.out.println("父类方法");
        }
    
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    
    }
    
    package com.zzm.day10.demo05;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:45
     * 创建人:张子默
     */
    public class Zi extends Fu {
    
        int num = 20;
    
        int age = 16;
    
        @Override
        public void showNum() {
            System.out.println(num);
        }
    
        @Override
        public void method() {
            System.out.println("子类方法");
        }
    
        public void methodZi() {
            System.out.println("子类特有方法");
        }
    }
    
    package com.zzm.day10.demo05;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:45
     * 创建人:张子默
     */
    
    /*
    访问成员变量的两种方式:
        1.直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
        2.间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
     */
    public class Demo01MultiField {
    
        public static void main(String[] args) {
            // 使用多态的写法,父类引用指向子类对象
            Fu obj = new Zi();
    
            System.out.println(obj.num);
            // System.out.println(obj.age); // 错误写法!
            System.out.println("===============");
    
            // 子类没有覆盖重写,就是父,10
            // 子类如果覆盖重写,就是子,20
            obj.showNum();
        }
    
    }
    
    package com.zzm.day10.demo05;
    
    /**
     * 用途:
     * 时间:2021/6/22 20:55
     * 创建人:张子默
     */
    
    /*
    在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。
    
    口诀:编译看左边,运行看右边。
    
    对比一下:
        成员变量:编译看左边,运行还看左边。
        成员方法:编译看左边,运行看右边。
     */
    public class Demo02MultiMethod {
    
        public static void main(String[] args) {
            Fu obj = new Zi(); // 多态
    
            obj.method(); // 父子都有,优先用子
            obj.methodFu(); // 子类没有,父类有,向上找到父类
    
            // 编译看左,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
            // obj.methodZi(); // 错误写法!
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 转型和instanceof关键字
    package com.zzm.day10.demo06;
    
    /**
     * 用途:
     * 时间:2021/6/22 21:18
     * 创建人:张子默
     */
    public abstract class Animal {
    
        public abstract void eat();
    
    }
    
    package com.zzm.day10.demo06;
    
    /**
     * 用途:
     * 时间:2021/6/22 21:18
     * 创建人:张子默
     */
    public class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    
        // 子类特有方法
        public void catchMouse() {
            System.out.println("猫抓老鼠");
        }
    }
    
    package com.zzm.day10.demo06;
    
    /**
     * 用途:
     * 时间:2021/6/22 21:32
     * 创建人:张子默
     */
    public class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("狗吃SHIT");
        }
    
        public void watchHouse() {
            System.out.println("狗看家");
        }
    }
    
    package com.zzm.day10.demo06;
    
    /**
     * 用途:
     * 时间:2021/6/22 21:19
     * 创建人:张子默
     */
    
    /*
    向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端。
    对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
    
    解决方案:用对象的向下转型【还原】。
     */
    public class Demo01Main {
    
        public static void main(String[] args) {
            // 对象的向上转型就是父类引用指向子类对象
            Animal animal = new Cat();
            animal.eat(); // 猫吃鱼
    
            // animal.catchMouse(); // 错误写法!
    
            // 向下转型,进行"还原"动作
            Cat cat = (Cat) animal;
            cat.catchMouse(); // 猫抓老鼠
    
            // 下面是错误的向下转型
            // 错误写法!编译不会报错,但是运行会出现异常
            // java.lang.ClassCastException,类转换异常
            Dog dog = (Dog) animal;
        }
    
    }
    
    package com.zzm.day10.demo06;
    
    /**
     * 用途:
     * 时间:2021/6/22 21:39
     * 创建人:张子默
     */
    
    /*
    获取一个父类的引用对象本来是什么子类。
        格式:
            对象 instanceof 类名称
        这将得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
     */
    public class Demo02Instanceof {
    
        public static void main(String[] args) {
            Animal animal = new Cat(); // 本来是一只猫
            animal.eat(); // 猫吃鱼
    
            // 如果希望调用子类特有方法,需要向下转型
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.watchHouse();
            }
    
            if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.catchMouse();
            }
    
            giveMeAPet(new Dog());
        }
    
        public static void giveMeAPet(Animal animal) {
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.watchHouse();
            }
    
            if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.catchMouse();
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
  • 相关阅读:
    SAS|proc sort(排序)&proc transpose(转置)
    【深入理解java虚拟机】 - 类加载器与双亲委派模型
    【技巧】PDF文件如何编辑?
    什么是IPQC(制程控制)?
    鸿蒙开发实例:【配置OpenHarmony SDK】
    PX4开源工程结构简明介绍
    Java内存模型——创建对象在堆区如何分配内存
    「Python循环结构」利用while循环求1~n的平方和
    [附源码]java毕业设计农村电商平台
    win10 pytorch安装说明
  • 原文地址:https://blog.csdn.net/a1448824839/article/details/125606417