Java之多态

时间:2024-09-29 15:23:18

文章目录

  • 1. 多态
    • 1.1 多态的概念
  • 2. 方法的重写
  • 3. 向上转型
    • 3.1
    • 3.2 发生向上转型的时机
  • 4. 动态绑定和静态绑定
  • 5. 什么是多态
    • 5.1
    • 5.2 多态的优缺点
  • 6. 避免在构造方法中调用重写的方法
  • 7. 向下转型![在这里插入图片描述](https://i-blog.****img.cn/direct/fd1fa83140d94f37ab3b881b0df41305.png)

1. 多态

1.1 多态的概念

去完成某个行为,不同的对象去完成会产生不同的状态。例如,猫和狗都在叫,而猫的叫的是喵喵,狗叫的是汪汪。

在讲解多态之前我们先学习什么是向上转型向下转型方法重写

2. 方法的重写

重写(override),也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写。

构成方法重写的条件

  1. 方法名相同
  2. 方法的参数列表相同(个数、顺序、类型)
  3. 方法的返回值相同

例如:
在这里插入图片描述
上述两个 bark 方法,实现了方法的重写。重写的方法通常会有 @Override 来修饰,表示方法的重写,可以用来提示重写的方法是否出现错误,如果出现错误@Override处会报错。
在这里插入图片描述
注意

  1. 静态方法不能被重写
  2. 被private修饰的方法不能被重写
  3. 被final修饰的方法不能被重写
  4. 如果方法被重写,子类的访问权限要大于等于父类的访问权限
    private < 包访问权限 < protected < public

跟重载是有区别的,注意区分哦
在这里插入图片描述

3. 向上转型

3.1

在这里插入图片描述
语法格式:父类类型 对象名 = new 子类类型()

public class Animal {
   public String name;
   public int age;

   public Animal(){

   }

   public Animal(String name,int age){
       this.name = name;
       this.age = age;
   }

   public void eat(){
       System.out.println(this.name + "正在吃饭");
   }

   public void bark(){
       System.out.println(this.name + "正在叫");
   }
}

public class Cat extends Animal{
	public Cat(){

    }

    public Cat(String name,int age){
        super(name,age);
    }
    
    public void bark(){
        System.out.println(this.name + "正在喵喵叫");
    }
}

public class Dog extends Animal{
	public Dog(){

    }
    public Dog(String name,int age){
        super(name, age);
    }
    @Override
    public void bark() {
        System.out.println(this.name + "正在汪汪叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();//实现了向上转型
        animal.bark();
    }
}

观察上述代码,Cat 类和 Dog 类中都重写了 bark 方法,肯定有人觉得main 方法中的 bark 方法可有可无,当我们删除这个 bark 方法时,animal无法访问bark这个方法。
在这里插入图片描述
无法访问的原因是在Animal当中没有该方法,通过父类引用访问的时候,只能访问父类自己特有的,所以我们不能删除。

是不是在等向下转型,因为向下转型不常用,所以放在最后面了
在这里插入图片描述

3.2 发生向上转型的时机

  1. 直接赋值
public class Main {
    public static void main(String[] args) {
        Animal animal = new Cat("zaizai",2);
    }
}
  1. 方法传参的时候
public class Main {
    public static void func(Animal animal){

    }
    public static void main(String[] args) {
        func(new Cat());
    }
}
  1. 返回值的时候
public class Main {
    public static Animal func(){
       return new Cat() ;
    }
    public static void main(String[] args) {
        func();
    }
}

不是只有直接赋值一种哦,要注意啦
在这里插入图片描述

向上转型的优点:让代码的实现更加简单
向上转型的缺点:无法调用到子类特有的方法

4. 动态绑定和静态绑定

当父类引用子类的对象(即向上转型)时,通过父类引用、调用重写的方法就实现了动态绑定

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

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

5. 什么是多态

5.1

回归正题,什么是多态呢
在这里插入图片描述
其实,上述代码就发生了多态,当 animal 引用的对象不一样,调用 eat 方法,表现的行为不一样,此时就叫多态。(同一个引用调用同一个方法表现的行为不一样

5.2 多态的优缺点

优点

  1. 降低圈复杂度(是一种用于评估代码复杂性的软件度量方法,代码的分支 / 判断越多,圈复杂度越高),避免使用大量的 if-else

例如:

public class Shape {
    public void draw(){
        System.out.println("画一个图形");
    }
}

public class Rectangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个矩形");
    }
}

public class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画一个三角形");
    }
}

public class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个圆");
    }
}

public class Main {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        drawMap(new Triangle());
        drawMap(new Rectangle());
        drawMap(new Circle());
    }
}

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

public static void drawMap() {
        Rectangle rectangle = new Rectangle();
        Triangle triangle = new Triangle();
        Circle circle = new Circle();

        String[] shapes = {"Rectangle","Circle","Circle","Triangle","Rectangle"};
         
        for(String shape:shapes){
            if(shape.equals("Rectangle"))
                rectangle.draw();
            else if(shape.equals("Circle"))
                circle.draw();
            else if(shape.equals("Triangle"))
                triangle.draw();
        }
}

基于多态

public static void drawMap(){
        Shape Rectangle = new Rectangle();
        Shape Triangle = new Triangle();
        Shape Circle = new Circle();

        Shape[] shapes = {Rectangle,Circle,Circle,Triangle,Rectangle};
        for(Shape shape:shapes){
            shape.draw();
        }

    }

果然基于多态方便了许多,没白学
在这里插入图片描述

不基于多态,圈复杂度高。基于多态,圈复杂度低,代码更简单。

  1. 可扩展性强
    当我们新增一个图形,创建一个新的实例就可以,改动代码较少。

缺点

  1. 属性没有多态性
    当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性

  2. 构造方法没有多态性

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

当我们在构造方法中调用重写的方法的代码如下:

public class B {
    public B(){
        func();
    }

    public void func(){
        System.out.println("B:func()");
    }
}

class D extends B{
    private int num = 1;
    public void func(){
        System.out.println("D:func()" + num);
    }
}

 class Main{
     public static void main(String[] args) {
         D d = new D();
     }
}

//运行结果
D:func()0

为什么会调用 D 的 func 方法呢?而且 num = 0 ,让我们来看一下它具体是怎么执行的
在这里插入图片描述

鸡爪子画图,宣!
在这里插入图片描述

步骤

  1. 构造D对象的同时,调用B的无参构造方法。
  2. B的构造方法中调用了 func 方法,此时触发动态绑定,调用 D 的 func 方法。
  3. 此时,D 对象自身还未构造,num 处于未初始化的状态,所以 num = 0

结论:尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题。

7. 向下转型在这里插入图片描述

语法格式:子类类型 对象名 = (子类类型)new 父类类型()
需要强转

例如:

Dog dog = (Dog)new Animal();

那么不强转为什么会报错呢?
我们可以理解为不是所有的动物都是狗,所以会报错。

但是向下转型非常不安全,例如

 Animal animal = new Cat();
 Dog dog = (Dog)new Animal();
 dog.bark();

虽然代码在编译层次上不会报错,但是运行结果为类型转换异常
在这里插入图片描述

Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.bark();

因为animal引用的本身就是Cat对象,所以不会报错。

Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

Animal animal = new Cat();
if(animal instanceof Cat){
     Cat cat = (Cat)animal;
     cat.bark();
}

上述语句表达的意思是,如果 animal 引用的是 Cat 类型……

虽然向下转型不安全,但是可以使用上述语句来解决问题。

终于完事了,我要奖励自己一下了
在这里插入图片描述