• Java多态


    1. 关于引用的进一步理解(交换值)
      因为Java方法在传递参数的时候都是值传递,那么如何通过方法实现2个数的值交换?
      明确:在传引用的时候,到底拿引用干了个啥
    class Value {
        public int a;
    }
    
    public class Test {
    
        public static void swap(Value value1, Value value2) {
            int tmp = value1.a;
            value1.a = value2.a;
            value2.a = tmp;
        }
    
        public static void main(String[] args) {
            //交换值,如果只是简单的定义a和b不太能做到,因为他们在栈上,我们要想办法把他们放到堆上
            Value value1 = new Value();
            Value value2 = new Value();
            value1.a = 10;
            value2.a = 20;
            swap(value1, value2);
            System.out.println(value1.a);
            System.out.println(value2.a);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    将属性a的权限改成private,此时在类外a是被封装起来的,不能够直接赋值了,可以提供get和set方法来实现交换值

    class Value {
        private int a;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    }
    
    public class Test {
    
        public static void swap(Value value1, Value value2) {
    //        int tmp = value1.a;
            int tmp = value1.getA();
    //        value1.a = value2.a;
            value1.setA(value2.getA());
    //        value2.a = tmp;
            value2.setA(tmp);
        }
    
        public static void main(String[] args) {
            //交换值,如果只是简单的定义a和b不太能做到,因为他们在栈上,我们要想办法把他们放到堆上
            Value value1 = new Value();
            Value value2 = new Value();
            //value1.a = 10;
            value1.setA(10);
            //value2.a = 20;
            value2.setA(20);
            swap(value1, value2);
            System.out.println(value1.getA());
            System.out.println(value2.getA());
        }
    
        public static void main1(String[] args) {
            Derived derived = new Derived();
            derived.fun();
        }
    }
    
    • 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

    运行结果:
    在这里插入图片描述

    1. 什么是多态
      发生多态的3个条件①在继承的条件下②发生向上转型③方法重写
      多态是一种思想,父类引用引用不同对象的时候,表现出来的行为是不一样的。【这就叫做多态】
      多态的前提是:动态绑定
      【一个父类引用 指向的对象不一样,调用重写的方法,会表现出不同的行为。】

    2. 向上转型
      分别定义动物类、狗类、鸟类

    class Aniaml {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "吃东西");
        }
    }
    
    class Dog extends Aniaml {
        public void wangwnag() {
            System.out.println(name + "汪汪汪");
        }
    }
    
    class Bird extends Aniaml {
        public String wing;
        public void miaomiao() {
            System.out.println(name + "喵喵喵");
        }
    }
    
    public class Test1 {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.name = "小狗";
            dog.eat();
            dog.wangwnag();
            System.out.println("===分割===");
            Bird bird = new Bird();
            bird.name = "小鸟";
            bird.eat();
            bird.miaomiao();
        }
    }
    
    
    • 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

    运行结果:
    在这里插入图片描述
    如果修改main方法中的实例化对象语句为:

            Aniaml aniaml1 = new Dog();
            aniaml1.name = "小狗";
            aniaml1.eat();
    //        aniaml1.wangwang();  //报错,因为只能访问Aniaml类中有的成员
            System.out.println("===分割===");
            Aniaml aniaml2 = new Bird();
            aniaml2.name = "小鸟";
            aniaml2.eat();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此时 Aniaml aniaml1 = new Dog();,左边是动物类,右边是狗类,发生了向上转型,父类引用指向了子类对象。
    理论上:等号两边的数据类型必须一致,否则赋值出错。
    当发生向上转型之后,此时通过父类的引用只能访问父类自己的成员,不能访问到子类特有的成员,也就是说,只animal能调用Animal类中的方法和属性,不能再访问Dog类和Bird类子类中独有的方法和属性了。

    1. 向上转型的3种场景
      ①直接赋值
            Aniaml aniaml1 = new Dog();
            aniaml1.name = "小狗";
            aniaml1.eat();
    
    • 1
    • 2
    • 3

    ②方法传参

        public static void fun(Aniaml aniaml) {
            aniaml.eat();
        }
    
        public static void main(String[] args) {
            Dog dog = new Dog("小狗");
            fun(dog);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ③方法返回值

        public static Aniaml func() {
            return new Dog();
        }
    
    • 1
    • 2
    • 3

    向上转型的优点:让代码实现更简单灵活。(animal能够引用它,说明它一定是个animal的子类,它一定是一个动物)
    向上转型的缺点:不能访问子类特有的方法。【除非发生了动态绑定,可以调用子类重写的方法】(animal只能调用自己特有的属性和方法)

    1. 方法重写的引入
      在上述例子种,狗和鸟都是吃饭,按照常理,狗应该吃狗粮,鸟应该吃鸟粮,此时就应该在狗类和鸟类种重写父类的eat方法。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
        public static void main(String[] args) {
            Aniaml aniaml1 = new Dog("小狗");
            aniaml1.eat();
            Aniaml aniaml2 = new Bird("小鸟");
            aniaml2.eat();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行结果:
    在这里插入图片描述
    分析:在这个过程中,【父类不是只能调用父类的方法吗?这里怎么调用了重写的方法呢?】因为这里发生了动态绑定,编译的时候确实调用的是Animal的eat方法,但是在运行的时候发生了动态绑定,调用了重写的方法。

    1. 动态绑定需要满足的3个条件
      ①向上转型②重写③通过父类引用调用这个父类和子类重写的方法
      【动态绑定是多态的基础】
      动态绑定:也称为后期绑定(晚绑定),在编译的时候不能确定方法的行为,需要等到程序运行的时候,才能确定具体调用哪个类的方法
      静态绑定:也称为前期绑定(早绑定),在编译的时候,根据传入的参数,就能够确定调用哪个方法,典型代表方法重载。

    2. 方法重写的注意点
      重写:是子类对父类非private修饰、非static修饰、非final修饰、非构造方法等进行重新编写。【重写的好处:子类可以根据需要,定义特定于自己的行为,子类可以根据需要实现父类的方法。】
      ①被private修饰的方法不能被重写:因为private权限范围只能在当前类内使用
      ②被static修饰的方法不能被重写:因为static属于类方法,如果被重写,则一个属于动物类,一个属于狗类,和对象毫无关系,分别属于各自所在的类。
      ③被final修饰的方法不能被重写:也叫密封方法
      ④构造方法不能被重写:因为子类的构造方法名字不可能和父类的构造方法的名字一样。
      ⑤子类的访问修饰限定权限要大于等于父类的权限
      private<默认

    3. 重写和重载的对比
      重写:①方法名相同 ②参数列表相同③返回值类型相同【当返回值类型不同的时候必须是 父子关系
      重载:①方法名相同②参数列表不同③与返回值类型无关
      【重写的参数列表 一定不能修改,重载的参数列表 必须修改】

    4. 向下转型【不安全】
      将一个子类对象经过向上转型之后当成父类方法使用,这也再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时需要将父类引用再还原为子类对象。
      例如,将Dog和Bird向上转型成Animal后,无法调用Dog中特有的wangwang方法以及Bird中的zhazha方法,此时需要将Animal再次向下转型成Dog和Bird进行调用子类特有的方法。

        public static void main(String[] args) {
            Aniaml aniaml1 = new Dog("小狗");
            Dog dog = (Dog) aniaml1;
            dog.eat();
            Aniaml aniaml2 = new Bird("小鸟");
            Bird bird = (Bird) aniaml2;
            bird.eat();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    分析:animal1向下转型:本来是狗,还原为狗,安全;animal2向下转型:本来是鸟,还原为鸟,安全。
    如果本来是狗,向下转型为鸟,则不安全:

        public static void main(String[] args) {
            Aniaml aniaml = new Dog("小狗");
            Bird bird = (Bird) aniaml;
            bird.eat();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编译没有报错,但是在运行的时候出现类型转换异常
    在这里插入图片描述
    分析:程序可以通过编程,但是运行时抛出异常,本来是狗,现在要强制还原为猫,无法正常还原,不安全。
    Java中为了提高向下转型的安全性,引入 instanceof关键字,如果表达式为true,则可以安全转换。

        public static void main(String[] args) {
            Aniaml aniaml = new Dog("小狗");
            if (aniaml instanceof Bird) {
                Bird bird = (Bird) aniaml;
                bird.eat();
            }else {
                Dog dog = (Dog) aniaml;
                dog.eat();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:
    在这里插入图片描述
    分析:aniaml instanceof Bird,判断animal是不是引用了Bird对象,如果引用了就可以向下转型。

    1. 多态的优缺点
      例:定义类,完成画图(圆,矩形,花等)
      使用多态完成:
    class Shape {
        public void draw() {
            System.out.println("画图形");
        }
    }
    
    
    class Cir extends Shape {
        @Override
        public void draw() {
            System.out.println("画圆");
        }
    }
    
    class Rex extends Shape {
        @Override
        public void draw() {
            System.out.println("画矩形");
        }
    }
    
    class Flow extends Shape {
        @Override
        public void draw() {
            System.out.println("画花");
        }
    }
    
    
    public class TestDemo {
    
        public static void func(Shape shape) {
            shape.draw();
        }
    
        public static void main(String[] args) {
            Cir cir = new Cir();
            Rex rex = new Rex();
            Flow flow = new Flow();
            
            func(cir);
            func(rex);
            func(flow);
    
    
        }
    }
    
    
    • 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

    运行结果:
    在这里插入图片描述

    圈复杂度:是一种描述一段代码复杂程度的方式,可以简单粗暴的一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”。如果一个方法圈复杂度太高,就需要考虑重构,不同公司对于代码的圈复杂度的规范不一样,一般不会超过10。
    假如不使用多态,则要使用到if-else语句等进行判断(此时圈复杂度会很高)。

    public static void main(String[] args) {
            Cir cir = new Cir();
            Rex rex = new Rex();
            Flow flow = new Flow();
            String[] shapes = {"cir", "rex", "flow","cir", "rex"};
    
            for (String x: shapes) {
                if (x.equals("cir")) {
                    cir.draw();
                }else if (x.equals("rex")) {
                    rex.draw();
                }else if (x.equals("flow")) {
                    flow.draw();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果使用多态,则不必写那么多if-else分支语句,代码更简单。

        public static void func(Shape shape) {
            shape.draw();
        }
    
        public static void main(String[] args) {
            Shape[] shapes = {new Cir(), new Rex(), new Flow(),new Rex(), new Rex()};
            for (Shape x: shapes) {
                func(x);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    分析: Shape数组:意味着每个元素都是Shope子类,父类类型的数组可以放子类类型变量。
    在func方法中,shape引用 引用饿子类对象不一样,调用的方法表现出来的行为也就不一样——这种思想叫做多态。

    如果要增加一个打印三角形。
    对于类的实现者:定义一个三角形的类,并重写方法。

    class Tri extends Shape {
        @Override
        public void draw() {
            System.out.println("画三角形");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对于类的调用者:创建一个三角形的实例。

            Tri tri = new Tri();
            func(tri);
    
    • 1
    • 2

    改动成本很低
    而对于不用多态的情况,就要把if-else进行一定的改动,改动成本更高。

    因此多态的优势:
    ①能够降低代码的“圈复杂度”,避免使用大量的if-else。
    ②可扩展能力强,如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。
    多态的缺陷:
    ①属性没有多态性,只有方法有多态性:当父类和子类都有同名的属性的时候,通过父类的引用,只能引用父类自己的成员属性。
    ②构造方法没有多态性,构造方法也不能被重写
    ③多态代码运行效率降低

    1. 避免在构造方法中调用重写的方法
      有坑的代码,创建两个类,B是父类,D是子类,D中重写func方法。并且在父类B的构造方法中调用func。
    class B {
    
        public B(){
            func();
        }
        public void func() {
            System.out.println("B的方法");
        }
    }
    
    class D extends B {
        int num;
        public D() {
            num = 1;
        }
        @Override
        public void func() {
            System.out.println(num + " C重写B的方法");
        }
    }
    
    
    public class TestDemo2 {
        public static void main(String[] args) {
    //B是父类,D是子类,D中重写func方法。并且在父类B的构造方法中调用func。
            D d = new D();
        }
    }
    
    
    • 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

    运行结果:
    在这里插入图片描述
    分析:在B的构造方法中调func,刻板印象会认为调用的是B的func方法,但是通过实际的运行结果发现调用的是D中的func方法,并且num的值为0,因为此时父类B的构造方法都还没执行完成,更别说子类D的构造了。
    注意:当在父类的构造方法当中,去调用父类和子类重写的方法的时候,此时会调用子类的重写方法。

  • 相关阅读:
    如何低成本的玩转独立站
    Mysql——》索引存储模型推演
    大数据之就业岗位
    解析java中的String类中的方法(一)
    【C语言】Windows下的C语言线程编程详解
    Vue 显示关键词附近内容并高亮显示关键词
    【Spring Security 系列】(三)剖析基础组件之授权功能
    高校新生如何选择最优手机流量卡?
    STM32F4系列单片机GPIO概述和寄存器分析
    电销外呼系统主要有哪些作用?
  • 原文地址:https://blog.csdn.net/weixin_44070116/article/details/127832662