从浅入深讲解Java多态

发布时间:2024年01月24日

一、什么是多态

多态顾名思义就是多种状态,那么在Java中可以解释为,不同的对象做相同的行为时,呈现出不同的状态。

比如说,狗和猫都做“叫”这个行为,那么狗是“汪汪叫”,猫是“喵喵叫”。这就是所谓的不同对象做相同行为时,呈现出了不同的状态。

那么在Java中如何做到多态呢?

在介绍实现多态的条件之前,我们需要讲一些前置的知识作为铺垫。

二、类对象的向上转型

1、向上转型

当一个类继承了某个父类的时候,才能够实现向上转型。

比如我们现在创建两个类,一个类是父类A,一个类是继承了父类A后的子类B。

class A{
    int a;
    int b;
        
}
class B extends A{
    int c;
    int d;
}

在这种情况下,我们去创建一个B类的对象b。那么这个’b’的类型是B。此时我们将其进行强转操作,将b的类型B向上强转为父类A。过程如下:

B b = new B();
A b1 = (A)b;

即,我们创建了一个B类对象b,然后将b转型为了父类A,并让一个类A的引用指向转型后的b。

上述两行代码可以合并,合并后更加直观:

A a = (A)new B();

这种由子类类型强转为父类类型的过程,叫做向上转型。

那么向上转型后,会有什么变化呢?

首先,我们准备两个类:

class Animal{
    public String name;
    public int age;
    
    public void eat(){
        System.out.println("动物在吃东西。");
    }
}

class Dog extends Animal{
    public String color;
    
    public void eat(){
        System.out.println("狗在吃东西。");
    }
    
    public void bark(){
        System.out.println("狗在叫。");
    }
}

此时,我们执行如下代码:

public static void main(String[] args) {
    Animal dog1 = (Animal) new Dog();
    System.out.println(dog1.name);
    System.out.println(dog1.age);
    System.out.println(dog1.color);//这一行出现了报错。
    dog1.eat();
    dog1.bark();//这一行报错。
}

我们发现,由于我们发生了向上转型,所以**子类特有的变量和方法无法访问了。**那么除此以外还有什么变化呢?我们观察到Animal中的eat()方法输出的是"动物在吃东西。“,而现在输出的却是"狗在吃东西。”。

这就说明,当子类重写父类方法后,执行的是子类重写后的方法。

那么我们可以做出如下总结:

当发生向上转型后,

(1)父类中没有,子类中有的变量和方法,无法访问。
(2)父类中有,子类中没有的变量和方法,访问的是父类的变量和方法。
(3)父类中有,子类中也有的变量和方法,优先调用子类的,如果子类没有,才会调用父类的。

2、向上转型的应用

我们看下面这段代码。
先准备三个类:

class Animal{
    public String name;
    public int age;
    public void eat(){
        System.out.println("动物在吃东西。");
    }
}

class Dog extends Animal{
    public void eat(){
        System.out.println("狗在吃东西。");
    }
}

class Cat extends Animal{
    public void eat(){
        System.out.println("猫在吃东西。");
    }

}

然后我们定义一个这样的方法。

    public static void test(Animal animal){
        animal.eat();
    }

接着我们在main函数中写下如下代码:

public static void main(String[] args) {
    Dog dog = new Dog();
    Cat cat = new Cat();
    test(dog);
    test(cat);
}

然后运行上述代码:
在这里插入图片描述

看完结果后,我们从两个方面来理解。

第一个方面:

在main函数中,我们发现,两个不同的类竟然都能做参数,这大大的提高了函数的复用性。按照我们的理解,参数应该只能接受相同类型的传参,那么我们应该写两个test函数,一个test负责Dog类的传入,另一个test负责Cat类的传入。

而现在由于父类的存在,二个不同的子类都发生了向上转型,使得在传入的过程中,两个不同的子类向上转型为了相同的父类。因此,我们只需要写一个test函数。

第二个方面:

在test函数中,我们发现都是调用了Animal类的eat()方法,这个函数中就输出了一句话,但是不同的子类调用,却输出了不同的语句。这就反映出了:不同的对象进行相同的行为,呈现出了不同的状态。这就是多态。

接下来,我们来总结一下实现多态的条件。

三、多态的形成条件

1、多态的实现必须依赖继承

刚刚代码中的DogCat都继承于Animal类。

2、子类必须要对父类中的方法进行重写

DogCat类中都重写了Animal中的eat方法。

3、通过父类类型的引用调用的方法

test函数中,我们的参数是父类Animal引用。我们通过这个引用调用了父类Animal中的eat方法。

四、重写与重载的区别

在这里插入图片描述

五、向下转型

1、定义

将一个子类对象经过向上转型之后当成父类方法使用,这个时候就无法调用子类的方法了,但有的时候我们可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换

代码如下:

Dog dog = new Dog();

//向上转型
Animal d1 = (Animal)dog;

//向下转型
Dog d2 = (Dog)d1;

但是,这里有一个安全性问题,我知道,子类只有一个父类,但是一个父类有很多子类。因此,子类向上转型的时候,转化后的父类是唯一的。但是,在你向下转型的时候,可能忘记了该类对应哪个子类,此时如果向下转化为了另外一个子类就出现了安全性问题。

比如,我们刚刚代码中的d1引用是由Dog转化过来的,而我们将其向下转化的时候,可能转化为了Cat。此时就出现了安全性问题。

//向上转型
Animal d1 = (Animal)dog;

//向下转型
Dog d2 = (Dog)d1;

//转化为了错误的子类,出现问题。
Cat d3 = (Cat)d1;

此时,你运行的话,终端会报错,如下:
在这里插入图片描述
意思就是这个包下的Dog类无法被转型为Cat类。

2、instanceof关键字

为了解决向下转型的安全性问题,就出现了instanceof关键字。

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

使用方法如下:

if(d1 instanceof Dog) {
    Dog d2 = (Dog) d1;
}
else if(d1 instanceof Cat)
{
    Cat d3 = (Cat)d1;
}

六、多态的优缺点

1、优点

**多态可以提高代码的简洁程度。**如果不用多态的话,就如刚刚的多态的代码示例一样,你要写很多个具有相同功能,只是参数类型不同的方法。有了多态以后,只需要将父类作为参数,其余子类向上转型即可。

**同时提高代码的可扩展能力。**如果你想对父类中的某个方法扩展,那你可以重新写一个扩展后的子类,然后在子类中的方法里重写扩展后的代码。调用的时候,只需要将其向上转型为父类,继续传入原来的方法即可。

2、缺点

使用了多态后,代码的运行效率降低。

七、最好不要在构造方法中调用重写的方法

我们最好不要在构造方法中调用重写的方法。 因为,子类构造的时候,优先调用父类构造方法,如果你在构造方法中调用了子类重写的方法。那么根据刚刚的解释,此时,将会执行子类中重写后的方法,如果说你子类方法中用到了某些成员变量,由于这个时候,你还没有执行父类构造方法,所以你的子类成员变量还是初始值。因此,你子类方法中,此时是用初始值执行后续代码的。

比如:

class Animal{
    public String name;
    public int age;
    public Animal(){
        eat();
    }
    public void eat(){
        System.out.println("动物在吃东西。");
    }
}

class Dog extends Animal{
    public int a;
    
    public Dog(){
        super();
        a = 10;
    }
    public void eat(){
        System.out.println(a);
        System.out.println("狗在吃东西。");
    }
}

父类构造中你调用了eat()方法,此时将会执行Dog中重写后的方法,这个方法中输出了a的值,而这个a在子类构造方法中赋值为了10,所以按逻辑来讲,我们这里期望的是输出10。但实际上这里输出0,因为你父类调用这个方法的时候,这个a还没来得及赋值。

验证结果如下:
在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_72060925/article/details/135735724
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。