一.多态的定义

多态定义: 是指同一行为,具有多个不同表现形式。

多态是继封装、继承之后,面向对象的第三大特性。

多态是出现在继承或者实现关系中的

二.多态的形式

作用:就是将父类作为对象类型,从而使这个对象可以接收所以子类的对象,从而减少代码的重复

多态体现的格式

父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

三.多态的前提:

  • 有继承关系,子类对象是可以赋值给父类类型的变量。

    例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。

  • 有父类引用子类对象

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

    Animal c = new Cat;

  • 有方法的重写

    这个针对多态根据类型调用不同类型定义的同名方法。

四.多态的使用场景

如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

多态的应用场景

有了多态之后,方法的形参就可以定义为共同的父类Person。

public void register(Person p){
    
}

这样person的子类对象在调用register时就可以直接调入了。

Student s = new Student;
Teacher t = new Teacher;
register(s);
register(t);

要注意的是:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(实现关系)。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

多态的应用场景

代码示例:

父类:
public class Person {
    private String name;
    private int age;

    空参构造
    带全部参数的构造
    get和set方法

    public void show(){
        System.out.println(name + ", " + age);
    }
}

子类1:
public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}

子类2:
public class Student extends Person{

    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}

子类3:
public class Teacher extends Person{

    @Override
    public void show() {
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}

测试类:
public class Test {
    public static void main(String[] args) {
        //创建三个对象,并调用register方法

        Student s = new Student();
        s.setName("张三");
        s.setAge(18);


        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);



        register(s);
        register(t);
        register(admin);


    }



    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类型的父类
    public static void register(Person p){
        p.show();
    }
}

五.多态的运行特点

调用成员变量时:编译看左边,运行看左边

  • 编译看左边:javac编译看左边的父类中有没有这个属性,没有就报错
  • 运行看右边:Java运行代码时,实际获取的是左边父类中的成员变量的值

    • Animal a = new Dog();:左边如果写父类,则直接去父类里面找name
    • Dog d = new Dog();:左边如果写子类,则优先在子类中找name,找不到,才会去父类里面找

原因:是用父类去创建的,所以a调用的自然也是父类里的变量而不是子类的。

例如:Animal a = new dog,用a.name调用的name实际是父类中的name

调用成员方法时:编译看左边,运行看右边

记住就好,这个定义有点模糊,不太好细说。
  • 编译看左边:javac编译看左边的父类中有没有这个方法,没有就报错
  • 运行看右边:Java运行代码时,实际获取的是子类中的方法

原因:如果子类对方法进行了重写,那么虚方法表中是会把父类的方法进行覆盖的,从而其实际获得的仍会是子类的方法。

例如:a.show

代码示例:

Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

六.多态的优点

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。

    例子:当需要将work的对象从Student改为Teacher时,只需要修改将Student()改为Teacher()即可,work方法中的对象也会随之改变。

    Person p = new Student();
    p.work();
  • 定义方法的时候,使用父类型作为参数,可以接收所以子类对象,体现了多态的扩展性与便利性。

    体现:

    • 使StringBuilder可以用append方法添加任意类型的东西

      它将Object作为创建对象的参数了。

      image-20231118111213676

    • 集合中如果不指定泛型的时候

      它将Object作为创建对象的参数了。

      随便写,怎么都行

      image-20231118111012463

七.多态的弊端

不能使用子类特有的功能

原因:

在多态中,调用成员方法时,他会先去看看左边类中(这里是父类)是否有这个类,没有就会直接报错。所以当调用只有子类有定义的方式,在多态运行的第一步就被卡住了(父类中找不到)

解决方法(强制转换)

详情见下面的引用类型转换

将对象的类型进行强制转换,转成子类原有的类型

语法:

子类类型 变量 = (子类类型)父类类型创建的对象

例如:

Student s =(Student)p;

注意点

  • 转换类型与真实的对象类型要一致,否则会报错

    例如:不能将狗转成猫

  • 转换时可以用instanceof关键字进行判断

我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

就像下面,用父类创建的a不能调用子类中特有的catchMouse()方法。
class Animal{
    public  void eat(){
        System.out.println("动物吃东西!")
    }
}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
}

八.引用类型转换

1. 向上转型(自动转换)

  • 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
    当父类引用指向一个子类对象时,便是向上转型。

使用格式:

父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。

2. 向下转型(强制转换)

  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
    一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

使用格式:

子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
   Cat c =(Cat) a;  

3. 案例演示

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

转型演示,代码如下:

定义类:

abstract class Animal {  
    abstract void eat();  
}  

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();                 // 调用的是 Cat 的 eat

        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse();         // 调用的是 Cat 的 catchMouse
    }  
}

4. 转型的异常

转换类型与真实的对象类型要一致,否则会报错

这是因为,明明。

示例代码:

结果:代码可以通过编译,但是运行时,却报出了 ClassCastException类型转换异常!

原因:最开始创建的是Cat类型对象,运行时,将其转换成Dog对象。

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

5. instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验

格式如下:

  • 如果变量属于该数据类型或者其子类类型,返回true。
  • 如果变量不属于该数据类型或者其子类类型,返回false。
变量名 instanceof 数据类型 

所以,转换前,我们最好先做一个判断,代码如下:

public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}

(1) instanceof新特性

JDK14的时候提出了新特性,把判断和强转合并成了一行

格式&含义:

  • 先判断a是否为Dog类型,如果是,则强制转成Dog类型,转换之后变量名为d
  • 如果不是,则不强转,结果直接是flase.

    a instanceof Dog d

示例代码:

//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
    d.lookHome();
}else if(a instanceof Cat c){
    c.catchMouse();
}else{
    System.out.println("没有这个类型,无法转换");
}
最后修改:2023 年 11 月 20 日
如果觉得我的文章对你有用,请随意赞赏