【JavaSE】多态

简介: 本文讲解:多态

 image.gif编辑

前言

大家好,我是程序猿爱打拳,今天给大家带来的是面向对象之封装继承多态中的多态。文章从多态的概念、多态的条件、重写、向上向下转型、多态的优缺点等来详细讲解多态,让我们继续学习面向对象的过程吧。

image.gif编辑

目录

1.多态的概念

2.多态的实现条件

3.重写

4.向上转型和向下转型

4.1向上转型

4.2向下转型

5.多态的优缺点

5.1避免繁琐的操作

5.2可扩展能力强

5.3多态缺点

6.避免在构造方法中调用重写的方法


1.多态的概念

多态以我们通俗的话来说,就是一种事物用多种形态来实现。在Java当中就是完成某个行为时用不同的对象去完成会出现不同情况。

image.gif编辑

一台打印机,当黑白打印机去实现打印图片功能时打印出来的是黑白图片。而彩色打印机实现打印图片功能时打印出来的是彩色图片。这就是一个功能,不同的实现,也是多态的思想。


2.多态的实现条件

在Java当中要实现多态,必须要满足以下几个条件:

    1. 必须在继承的体现下实现
    2. 子类必须要对父类中的方法进行重写
    3. 通过父类的引用调用重写的方法

    多态的体现,在代码的运行时,由于传递的参数不同,会调用不同的类。

    class Animal {
        public void eat() {
            System.out.println("吃饭啦!");
        }
    }
    class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("吃狗粮啦!");
        }
    }
    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("吃猫粮啦!");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal an1 = new Dog();
            Animal an2 = new Cat();
            Animal an3 = new Animal();
            an1.eat();
            an2.eat();
            an3.eat();
        }
    }

    image.gif

    运行后输出:

    image.gif编辑

    以上程序满足,多态的几个基本条件。实现的就是一个简单的重写,也就是父类和子类有相同的方法。下面我就来具体讲解重写的用法。


    3.重写

    重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

    重写的满足条件:

      1. 参数列表相同
      2. 方法名相同
      3. 返回类型相同

      【重写与重载的区别】

      区别 重写(oberride) 重载()
      参数列表 一定不能被修改 必须修改
      返回类型 一定不能修改除非可以构成父子类关系 可以修改
      访问限定修饰符 一定不能修改更严格的限制 可以修改

      早期的QQ只能发信息,而今天的QQ在发信息的过程中可以看到对方的状态如正在输入信息、正在听歌等等,这是一个动态绑定的思想。

      image.gif编辑

      静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

      动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。典型代表函数重写。


      4.向上转型和向下转型

      4.1向上转型

      向上转型实际上就是创建一个子类对象,将其当作父类来使用。语法格式为:父类类型 对象名 = new 子类类型();

      class Animal {
          public String name;
          public void eat() {
              System.out.println(name+"吃饭啦!");
          }
      }
      class Dog extends Animal {
          @Override
          public void eat() {
              System.out.println(name+"吃狗粮啦!");
          }
      }
      class Cat extends Animal {
          @Override
          public void eat() {
              System.out.println(name+"吃猫粮啦!");
          }
      }
      public class Test {
          public static void main(String[] args) {
              Animal an1 = new Dog();
              Animal an2 = new Cat();
      }

      image.gif

      在上述代码中的main方法中,an1和an2就是一个子类对象当作父类类型来使用,也就是向上转型的一个体现,因此

      image.gif编辑

      当我们的 编译器中出现上图左侧这样的标志时表示着我们的代码向上转型了,是重写的体现标志。向上转型的使用场景为:直接赋值、方法传参、方法返回

      (1)直接赋值

      class Animal {
          public String name;
          public int age;
          public Animal(String name, int age) {
              this.name =name;
              this.age = age;
          }
          public void eat() {
              System.out.println(name+"吃饭啦!");
          }
      }
      class Dog extends Animal {
          public Dog(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃狗粮啦!");
          }
      }
      class Cat extends Animal {
          public Cat(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃猫粮啦!");
          }
      }
      public class Test {
          public static void main(String[] args) {
              Animal an1 = new Dog("旺财",3);
              Animal an2 = new Cat("小眯",2);
              an1.eat();
              an2.eat();
          }
      }

      image.gif

      运行后输出:

      image.gif编辑以上代码展示了直接赋值的效果,在main方法中直接在实例化Dog和Cat时,把参数传给各个子类,达到了向上转型的效果。


      (2)方法传参

      class Animal {
          public String name;
          public int age;
          public Animal(String name, int age) {
              this.name =name;
              this.age = age;
          }
          public void eat() {
              System.out.println(name+"吃饭啦!");
          }
      }
      class Dog extends Animal {
          public Dog(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃狗粮啦!");
          }
      }
      class Cat extends Animal {
          public Cat(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃猫粮啦!");
          }
      }
      public class Test {
          //通过方法传参
          public static void eatFood(Animal str) {
              str.eat();
          }
          public static void main(String[] args) {
              //直接赋值
              Animal an1 = new Dog("旺财",3);
              Animal an2 = new Cat("小眯",2);
              eatFood(an1);
              eatFood(an2);
          }
      }

      image.gif

      运行后输出:

      image.gif编辑

      以上代码中的main方法,我添加了一个eatFood方法。在程序运行前这个方法是里面的str只是一个引用,程序运行后会一次把main方法中的an1和an2传给str,因此达到了一个引用实现两个对象的效果。这样也可以达到个向上转型。

      因此,当父类引用的对象不一样时表现出的行为是不一样的,这就是多态的思想。


      4.2向下转型

      当我们将一个子类经过向上转型后当成父类的方法使用时,再无法调用子类方法了。当时当我们需要调用子类方法中的特性时,就会形成一个向下转型的效果。

      class Animal {
          public String name;
          public int age;
          public Animal(String name, int age) {
              this.name =name;
              this.age = age;
          }
          public void eat() {
              System.out.println(name+"吃饭啦!");
          }
      }
      class Dog extends Animal {
          public Dog(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃狗粮啦!");
          }
      }
      class Cat extends Animal {
          public Cat(String name,int age) {
              super(name,age);
          }
          @Override
          public void eat() {
              System.out.println(name+"吃猫粮啦!");
          }
      }
      public class Test {
          public static void main(String[] args) {
              //直接赋值
              Animal an1 = new Dog("旺财",3);
              Animal an2 = new Cat("小眯",2);
              Animal an3 = new Animal("阿虎",4);
              //向下转型,输出了子类的特性
              an1.eat();
              //向下转型,输出了子类的特性
              an2.eat();
              //输出了父类自己的特性
              an3.eat();
          }
      }

      image.gif

      运行后输出:

      image.gif编辑以上代码main方法中,所有的引用都是父类中的eat()方法。但是Dog和Cat有自己的特性,因此父类根据子类特有的特性输出了Dog和Cat这两个子类中的eat()方法,这就是向下转型的体现。

      重写的注意项:

        1. private修饰的方法不能重写
        2. static修饰的方法不能重写
        3. 子类的访问修饰限定权限要大于等于父类的权限
        4. final修饰的方法不能被重写,我们称之密封方法

        5.多态的优缺点

        假设有以下代码,画图形:

        class Shape {
            public void draw() {
                System.out.println("画图型");
            }
        }
        class Rect extends Shape {
            @Override
            public void draw() {
                System.out.println("画菱形");
            }
        }
        class Cycle extends Shape {
            @Override
            public void draw() {
                System.out.println("画圆形");
            }
        }
        class Flower extends Shape {
            @Override
            public void draw() {
                System.out.println("画花儿");
            }
        }
        public class Test {
            public static void main(String[] args) {
                Rect rect = new Rect();
                Cycle cycle = new Cycle();
                Flower flower = new Flower();
                String[] sharpes = {"cycle","rect","flower"};
                for (String shape: sharpes) {
                    if (shape.equals("cycle")) {
                        cycle.draw();
                    }else if(shape.equals("rect")) {
                        rect.draw();
                    }else if(shape.equals("flower")) {
                        flower.draw();
                    }
                }
            }
        }

        image.gif

        运行后输出:

        image.gif编辑

        我们发现这样去编写代码,非常的繁琐,要不停的用法if elseif等等,当我们使用多态后,就能避免这样繁琐的操作。


        5.1避免繁琐的操作

        class Shape {
            public void draw() {
                System.out.println("画图型");
            }
        }
        class Rect extends Shape {
            @Override
            public void draw() {
                System.out.println("画菱形");
            }
        }
        class Cycle extends Shape {
            @Override
            public void draw() {
                System.out.println("画圆形");
            }
        }
        class Flower extends Shape {
            @Override
            public void draw() {
                System.out.println("画花儿");
            }
        }
        public class Test {
            public static void main(String[] args) {
                Shape[] shapes = {new Rect(),new Cycle(),new Flower()};
                for (Shape shape: shapes) {
                    shape.draw();
                }
            }
        }

        image.gif

        运行后输出:

        image.gif编辑 我们发现,使用多态后能够降低代码的 "圈复杂度", 避免使用大量的 if - else分支语句。


        5.2可扩展能力强

        如果我们要新增一种新的形状,使用多态的方式代码的改动成本也比较低,如增加一个三角形:

        class Triangle extends Shape {
            @Override
            public void draw() {
                System.out.println("画三角形");
            }
        }
        public class Test {
            public static void main(String[] args) {
                Shape[] shapes = {new Rect(),new Cycle(),new Flower(),new Triangle()};
                for (Shape shape: shapes) {
                    shape.draw();
                }
            }
        }

        image.gif

        我们只需要新增一个类和增加一个对象即可。


        5.3多态缺点

          1. 代码的运行效率降低
          2. 属性没有多态性,当父类和子类都有同名属性的时候,通过父类的引用只能引用父类自己的成员属性
          3. 构造方法没有多态性,见下方讲解

          6.避免在构造方法中调用重写的方法

          class A {
              public A() {
                  func();
              }
              public void func() {
                  System.out.println("A的func");
              }
          }
          class B extends A {
              private int num = 10;
              @Override
              public void func() {
                  System.out.println("B的func"+num);
              }
          }
          public class Test {
              public static void main(String[] args) {
                  B b = new B();
              }
          }

          image.gif

          运行后输出:

          image.gif编辑

          构造方法B对象的同时,会调用A的构造方法。A的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到B中的func此时 B 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.所以在构造函数内,尽量避免使用实例方法,除了final和private方法。


          那么本期博客都这里就结束了,感谢各位的耐心阅读。

          image.gif编辑

          下期预告:抽象类与接口

          相关文章
          |
          11月前
          |
          Java 编译器
          【JavaSE】面向对象之多态
          【JavaSE】面向对象之多态
          |
          存储 Java 编译器
          【javaSE】 类和对象详解(下)
          【javaSE】 类和对象详解(下)
          |
          6月前
          javaSE&多态
          javaSE&多态
          34 1
          |
          安全 Java
          【JAVASE】多态 下
          【JAVASE】多态
          |
          6月前
          |
          Java
          JavaSE学习之--继承和多态(二)
          JavaSE学习之--继承和多态(二)
          69 0
          |
          6月前
          |
          Java 编译器
          JavaSE学习之--继承和多态(一)
          JavaSE学习之--继承和多态
          62 0
          |
          6月前
          |
          Java 编译器
          JavaSE学习之--继承和多态(三)
          JavaSE学习之--继承和多态(三)
          58 0
          |
          11月前
          |
          安全 Java 程序员
          JavaSE继承和多态
          JavaSE继承和多态
          |
          11月前
          |
          Java 程序员 编译器
          【JavaSE】面向对象之继承
          【JavaSE】面向对象之继承
          |
          存储 安全 Java
          类和对象【JavaSE】
          类和对象【JavaSE】
          78 0