day18_面向对象的三大特征之多态

多态概述

多态是继封装、继承之后,面向对象的第三大特性。生活中,比如求面积的功能,圆、矩形、三角形实现起来是不一样的。跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态

定义

  • 多态 是指同一行为,对于不同的对象具有多个不同表现形式。
  • 程序中多态: 是指同一方法,对于不同的对象具有不同的实现.

前提条件

  • 继承或者实现父类
  • 引用指向子类对象\接口引用指向实现类对象
  • 方法的重写

实现多态


代码示例

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) {
           /*
            多态: 同一种行为,不同的事物具有不同的表现形态
            实现多态:
                1.继承或者实现
                2.父类引用指向子类对象\接口引用指向实现类对象
                3.方法重写
         */
        // 父类引用指向子类对象
        Animal dog = new Dog();
        dog.eat();//狗吃骨头
        Animal cat = new Cat();
        cat.eat();//猫吃鱼
    }
}

编译时类型与运行时类型不一致问题

  • 编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;
  • 运行时看“子类”,一定是执行子类重写的方法体;

多态时访问成员的特点

class Fu {
    int a = 10;

    public static void method1() {
        System.out.println("我是父类静态方法");
    }

    public void method2() {
        System.out.println("我是父类非静态方法");
    }

}

class Zi extends Fu {
    int a = 20;

    public static void method1() {
        System.out.println("我是子类静态方法");
    }

    public void method2() {
        System.out.println("我是子类非静态方法");
    }
}

public class Test {
    public static void main(String[] args) {
        //多态
        Fu Demo = new Zi();
        System.out.println(Demo.a);//10
        Demo.method1();//我是父类静态方法
        Demo.method2();//我是子类非静态方法
    }
}

多态时成员变量的访问特点

  • 编译看左边,运行看左边简而言之:多态的情况下,访问的是父类的成员变量

简单记多态时成员方法的访问特点

  • 非静态方法:编译的时候去父类中查找方法,运行的时候优先去子类中查找方法来执行
  • 静态方法:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行

多态的应用

如果变量的类型为父类类型,该变量就可以接收该父类类型的对象或者其所有子类对象

Object obj = new Car();

多态应用在形参实参:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象

public class Test {
    public static void main(String[] args) {
        // 形参多态:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象
        Dog d = new Dog();
        method(d);

        System.out.println("===============================");

        Cat c = new Cat();
        method(c);
    }

    // 需求: 定义一个方法,带有一个参数,该参数可以接收Animal类对象以及Animal类的所有子类对象
    // method(d); ====实参赋值给形参的时候==> Animal anl = new Dog();
    // method(c); ====实参赋值给形参的时候==> Animal anl = new Cat();
    public static void method(Animal anl){
        anl.eat();
    }

}

多态应用在返回值:如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象

/*
     * 设计一个方法,可以购买各种动物的对象,此时不确定是那种具体的动物
     * 
     * 返回值类型是父类的对象
     * 
     * 多态体现在   返回值类型  Animal ,实际返回的对象是子类的new Cat(),或new Dog()
     */
    public static Animal buy(String name){
        if("猫咪".equals(name)){
            return new Cat();
        }else if("小狗".equals(name)){
            return new Dog();
        }
        return null;
    }

多态应用在数组:数组元素类型声明为父类类型,可以存储父类类型和其子类类型

Animal[] arr = new Animal[2]; //在堆中开辟了长度为5的数组空间,用来装Animal或它子类对象的地址

arr[0] = new Cat();//多态引用   左边arr[0] 是Animal类型,右边是new Cat()
                            
arr[1] = new Dog();

一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。这个和基本数据类型的转换是不同的。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

多态的好处和弊端

  • 好处:实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。
  • 弊端:多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。

代码示例

class Animal{//父类
    public void eat(){
        System.out.println("吃东西");
    }
}

//子类
class Cat extends Animal{
    @Override
    public void eat() {//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchFish(){
        System.out.println("猫会抓鱼");
    }
}
public class Test {
    public static void main(String[] args) {
           /*
            多态: 同一种行为,不同的事物具有不同的表现形态
            实现多态:
                1.继承或者实现
                2.父类引用指向子类对象\接口引用指向实现类对象
                3.方法重写
         */
        // 父类引用指向子类对象
        Animal cat = new Cat();
        cat.eat();//猫吃鱼
        //cat.catchFish(); 编译报错,因为多态成员访问的特点是,编译看父类,而父类中没有子类独有的功能
    }
}

解决弊端的方式:

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。

向上转型当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

  • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型
  • 此时,一定是安全的,而且也是自动完成的
         // 向上转型  
        Animal a = new Cat();  

向下转型当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型

  • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型
  • 此时,不一定是安全的,需要使用(类型)进行强制类型转换
  • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断、
 Aniaml anl = new Cat();  
 Cat c = (Cat)anl;//向下转型

为了避免ClassCastException的发生Java提供了 instanceof`关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

编辑

执行流程:

  •  判断前面变量指向的对象类型是否是后面的数据类型:
  •  如果前面变量指向的对象类型是属于后面的数据类型,那么就返回true
  •  如果前面变量指向的对象类型不是属于后面的数据类型,那么就返回false

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

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
        }
    }  
}

 多态的应用场景综合案例

package demo07;

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

    // 特有的功能
    public void lookHome(){
        System.out.println("狗在看家...");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼...");
    }
    // 特有的功能
    public void catchMouse(){
        System.out.println("猫抓老鼠...");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog d = new Dog();
        method(d);

        System.out.println("==========================");

        Cat c = new Cat();
        method(c);
    }

    // 形参多态: 如果父类类型作为方法的形参类型,那么就可以接收该父类类型的对象或者其所有子类的对象
    public static void method(Animal anl){
        anl.eat();
        if (anl instanceof Dog){
            Dog d = (Dog)anl;// 向下转型 Dog类型
            d.lookHome();
        }

        if (anl instanceof Cat){
            Cat c = (Cat)anl;// 向下转型 Cat类型
            c.catchMouse();
        }

    }
}
posted @ 2022-07-04 15:57  我的脚印  阅读(17)  评论(0)    收藏  举报