Java:理解多态

目录

1、向上转型:

2、向下转型

3、运行时绑定(动态绑定)

4、重写

5、多态

1、理解多态(polypeptide)

2、多态的好处


1、向上转型:

向上转型发生的时机:

          1、 直接赋值 

          2、方法传参

           3、方法返回

1、直接赋值

class Animal{
    public String name;
    private int age;
}
class Dog extends Animal{
    public int leg;//...指代狗的特有属性
    public void wangwang(){
        System.out.println("wang!");
    }//指代狗的特有方法
}
public class TestDemo {
    public static void main(String[] args) {
        Dog dog=new Dog();
        //直接赋值
        Animal animal=dog;
        Animal animal=new Dog();
    }
}

2、方法传参:

class Animal{
    //父类
}
class Dog extends Animal{
   //子类
}
public class TestDemo {
    public  static void func(Animal animal){
        
    }

    public static void main(String[] args) {
        Dog dog=new Dog();
        //方法传参
        Animal animal=new Dog();
        func(animal);
        func(new Dog());
        func(dog);
    }
}

此时形参 animal 的类型是 Dog(基类), 实际上对应到 Animal(父类) 的实例。

 3、方法返回

class Animal{
    //父类
}
class Dog extends Animal{
    //子类
}
public class TestDemo {
   
    //方法返回
    public static Animal func1(){
        return new Dog();
    }
    
    public static void main(String[] args) {
    }
}

此时方法func1 返回的是一个 Animal 类型的引用, 但是实际上对应到 Dog的实例。

2、向下转型

向下转型就是父类对象转成子类对象

class Animal{
    //父类
}
class Dog extends Animal{
    public void wang(){
        System.out.println("wang!");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Animal animal=new Dog();
        //animal.wang();//报错
        //向下转型
        // (Dog) 表示强制类型转换
        Dog dog =(Dog) animal;//父类赋给子类
        dog.wang();
    }
}
  • 注:在强转之前, animal 的类型是 Animal, 此时编译器 没有 wang() 方法,虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的。

不安全性:(上面的例子之所以没有异常是 animal 本质上是Dog,才可以引用Dog的方法)

如下:animal 本质上引用的是一个 Dog 对象,是不能转成 Bird 对象的,运行时就会抛出异常。

class Animal{
    //父类
}
class Dog extends Animal{
    public void wang(){
        System.out.println("wang!");
    }
}
class Bird extends Animal{
    public void fly(){
        System.out.println("fly");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Animal animal=new Dog();
        //向下转型
        Bird bird=(Bird)animal;
        bird.fly();
    }
}

运行时异常:(类型转换异常)

所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换:(如下:animal不是Bird的实例,运行结果为空)

class Animal{
    //父类
}
class Dog extends Animal{
    public void wang(){
        System.out.println("wang!");
    }
}
class Bird extends Animal{
    public void fly(){
        System.out.println("fly");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Animal animal=new Dog();
        if(animal instanceof Bird) {
            //判断animal 是不是Bird的实例;animal是不是Bird的实例对像
            Bird bird = (Bird) animal;
            bird.fly();
        }
    }
}

3、运行时绑定(动态绑定)

  • 父类引用引用子类对象
  • 通过父类引用 调用父类和子类的同名覆盖方法。

此时就会发生运行时绑定 这也是多态的前提。

在构造方法中调用重写的方法

class Animal{
    public Animal() {
        eat();
        System.out.println("父类构造");
    }
    public void eat(){
        System.out.println("父类中的eat");
    }
}
class Dog extends Animal{
    public Dog(){
        super();
        System.out.println("子类构造");
    }
    public void eat(){
        System.out.println("子类的eat(重写的)");

    }
}
public class TestDemo{
    public static void main(String[] args) {
        Animal animal=new Dog();
    }
}
//运行结果
子类的eat(重写的)
父类构造
子类构造

         运行时,先调用Dog不带参数的构造方法(即②),但在此方法中    先要帮助父类构造一个不带参数的构造方法super()(即①),在①方法中先去调用了eat()方法,此时调用的eat方法是子类中重写的方法(即④)(触发运行时绑定),输出“子类中的eat”(eat调用完毕),然后输出“父类构造”(父类构造完毕),      然后输出“子类构造”。

4、重写

      针对刚才的 eat 方法来说: 子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)。

重写(override):覆盖、覆盖

1、方法名相同

2、参数列表相同

3、返回值相同 

注:

权限大小:private <  default < protected < public

  • 子类的访问权限一定要大于等于父类的访问权限
  • 要重写的方法一定不可以是static方法
  • 要重写的方法一定不可以被final修饰

重写与重载的区别


5、多态

1、理解多态(polypeptide)

我们可以写一些只关注父类的代码, 就能够同时兼容各种子类的情况。

代码示例: 打印多种形状:

class Shap{
    public void draw(){
        System.out.println("父类draw");
    }
}
class Rect extends Shap{
    //方法中没有重写draw()
}
class Circle extends Shap{
    @Override//注解:表示下面的这个方法是重写的
    public void draw(){
        System.out.println("○");
    }
}
class Flower extends Shap{
    @Override//注解:表示下面的这个方法是重写的
    public void draw(){
        System.out.println("❀");
    }
}

//...................分割线.................
public class TestDemo1 {
    public static void drawMap(Shap shap){
        shap.draw();
    }
    public static void main(String[] args) {
        Rect rect=new Rect();//Rect中未重写draw()
        drawMap(rect);//此时调用的就是父类的draw()

        Circle circle=new Circle();//Circle中重写了draw()
        drawMap(circle);//此时调用的就是Circle中的draw()

        Flower flower=new Flower();
        drawMap(flower);
    }
}
//运行结果:
父类draw
○
❀

      上面代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的,

public static void drawMap(Shap shap){
        shap.draw();
    }

     当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例,此时 shape 这个引用调用 draw 方法(  shap.draw()  )时,可能会有多种不同的表现 (和 shape 对应的实例相关), 这种行为就称为多态。

     多态顾名思义, 就是 "一个引用, 能表现出多种不同形态"。

2、多态的好处

1) 类调用者对类的使用成本进一步降低

  • 封装是让类的调用者不需要知道类的实现细节.
  • 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。

因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。

2) 能够降低代码的 "圈复杂度", 避免使用大量的 if - else 。

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下

public static void drawShapes() {
 Rect rect = new Rect();
 Cycle cycle = new Cycle();
 Flower flower = new Flower();
 String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};

 for (String shape : shapes) {
 if (shape.equals("cycle")) {
 cycle.draw();
 } else if (shape.equals("rect")) {
 rect.draw();
 } else if (shape.equals("flower")) {
 flower.draw();
 }
 }
}

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

public static void drawShapes() {
 // 我们创建了一个 Shape 对象的数组.
 Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
 new Rect(), new Flower()};
 for (Shape shape : shapes) {
 shape.draw();
 }
}

3) 可扩展能力更强

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低,对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了。

class Triangle extends Shape {
 @Override
 public void draw() {
 System.out.println("△");
 }
} 

猜你喜欢

转载自blog.csdn.net/Aug_IK/article/details/113094833