面向对象进阶--多态(Java中的多态)详解

 一、多态

1.1 多态的形式

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

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

多态体现的格式

父类类型 变量名 = new 子类
变量名.方法名();

  • 多态体现为父类引用变量可以指向子类对象:定义了一个父类类型的引用,指向新建的子类类型的对象,由于子类是继承他的父类的,所以父类类型的引用是可以指向子类类型的对象的

多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。  

1.2 多态的使用场景

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

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

要注意的是:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。

  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。

  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

代码示例:

父类:
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();
    }
}

1.3 多态的定义

多态是什么?

  • 多态是同一个行为具有不同的表现形式或形态的能力
  • 同一方法可以根据发送对象的不同而采用不同的行为方式

例如:打印机分为黑白打印机和彩色打印机,在黑白打印机情况下打出来为黑白,在彩色打印机情况下打印出来为彩色

 多态就是事物的多种形态,一个对象在不同条件下所表现的不同形式

1.4多态的存在条件

多态存在的三个必要条件

  1. 继承或实现:在多态中必须存在有继承或实现关系的子类和父类
  2. 方法的重写:子类对父类中的某些方法进行重新定义(重写,使用@Override注解进行重写)
  3. 基类引用指向派生类对象,即父类引用指向子类对象,父类类型:指子类对象继承的父类类型,或实现的父接口类型

 多态的格式 

  • 父类类型  变量名 = new 子类类型();
  • 然后通过 变量名.方法名()调用在子类中重写的方法
  • 多态体现为父类引用变量可以指向子类对象:定义了一个父类类型的引用,指向新建的子类类型的对象,由于子类是继承他的父类的,所以父类类型的引用是可以指向子类类型的对象的

1.5多态中的成员特点

此处举例Animal是父类,Dog是子类

Animal dog = new Dog();   //Animal是引用类型,Dog是实际类型
System.out.println(dog.age) //dog的引用类型是Animal,所以取到的是父类Animal中的值,说白了dog是属于Animal类,Animal中变量的值是多少就通过对象就取得多少

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

  • 多态成员变量:编译运行看左边

再举一个例子

父类Animal:

//父类
public class Animal {
 
    public int age = 11;
    
}

子类Dog:

//子类
public class Dog extends Animal {
 
    public int age = 33;
    
}

Test类:

public class DemoApplication {
 
    public static void main(String[] args) {
 
        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
        System.out.println(dog.age);
    }
}

控制台打印输出:父类中定义的age 

11  

此处举例Animal是父类,Dog是子类

Animal dog = new Dog();   //Animal是引用类型,Dog是实际类型
System.out.println(dog.age) //dog的引用类型是Animal,所以取到的是父类Animal中的值,说白了dog是属于Animal类,Animal中变量的值是多少就通过对象就取得多少

 

  • 多态成员方法:编译看左边,运行看右边

Animal a =new Dog()
Fu  f=new Zi()
如果是父new出一个子 就是调用成员变量的时候,直接去父类里面去找变量
如果是子new出一个子 就是先去自己类也就是子类去找变量,找不到才去父类 

此处举例Animal是父类,Dog是子类

Animal  dog  =  new Dog();  //Animal是引用类型,Dog是实际类型
dog.eat();     //变量dog的实际类型是Dog,即是由Dog 这个实际类型new出来的,因此dog.eat() 调用的应该是子类Dog中重写的方法

父类Animal:

//父类
public class Animal {
 
 
 
    public void eat() {
        System.out.println("午餐吃狗粮");
    }
 
}

子类Dog: 

//子类
public class Dog extends Animal {
    
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

public class DemoApplication {
 
    public static void main(String[] args) {
 
        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
 
        dog.eat();
    }
}

控制台打印输出:调用的是子类中重写的方法 

 晚餐吃狗粮

1.6多态的优势

当我们调用一个方法的时候,发现一个方法的形参是一个类名,那么我们就可以传递这个类所有的子对象

  1. 多态情况下,子类和父类存在同名的成员变量时,访问的时父类的成员变量
  2. 多态情况下,子父类存在同名的非静态成员方法时,访问的是子类中重写的方法
  3. 多态情况下,子父类存在同名的静态成员变量成员方法时,访问的是父类的成员函数
  4. 多态情况下,不能访问子类独由的方法

 

对于子类独有的方法,父类无法访问

public class Test {
    public static void main(String[] args) {
      //创建对象
        Animal a=new Dog();
        //编译看左边,运行看右边
        a.eat();//打印的是狗在啃骨头
    }
}

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("猫在吃鱼");
    }
}

1.7多态的弊端

不能调用子类的特有功能

package itheima.a02polymorphismdemo2;

public class Test {
    public static void main(String[] args) {
        //创建对象
        Animal a = new Dog();
        //编译看左边,运行看右边
        a.eat();//打印的是狗在啃骨头

        //多态的弊端
        //不能调用子类的特有功能
        //当调用成员的方法的时候,编译看左边,运行看右边
        //那么编译的时候首先会检查左边的父类有没有这个方法,如果没有直接报错

        //解决方法
        //变回子类类型即可
        //比如int a=10,把a转换为byte 强制即可
        //int b=(byte)a;
        //Dog d=(Dog)a
        //转换的时候不能瞎转,如果转成其他类的类型,就会报错
        /*if (a instanceof Dog) {
            Dog d = (Dog) a;
            d.lookhome();

        } else if (a instanceof cat) {
            cat c = (cat) a;
            c.sleep();
        } else {
            System.out.println("没有这个类型,无法转换");
        }

*/
        //新特性
        //先判断a是否为Dog类型,如果是,则强转为Dog类型,转换之后的变量名为d
        //如果不是,则不强制,结果直接是false
        if (a instanceof Dog d) {
            d.lookhome();

        } else if (a instanceof cat c) {
            c.sleep();
        } else {
            System.out.println("没有这个类型,无法转换");
        }
    }
}

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 sleep() {
        System.out.println("猫在睡觉");
    }
}

启动项:walk()方法爆红,即编译报错

 
public class DemoApplication {
 
    public static void main(String[] args) {
 
        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
 
        dog.eat();  //访问的是子类中重写的方法
 
 
        dog.walk();  //walk方法爆红,即编译报错,编译看左边,dog类的实例对象Animal没有walk()这个方法,所以编译报错
 
 
    }
}

根据多态成员方法中编译看左边,运行看右边的原理

Animal  dog  =  new Dog();

可知 左边的Animal引用类型中没有walk()这个方法,故编译不通过,编译爆红

父类Animal保持不变:

//父类
public class Animal {
    
    public void eat() {
        System.out.println("午餐吃狗粮");
    }
 
}

子类Dog:增加子类读有的方法walk()

//子类
public class Dog extends Animal {
 
    
    public void walk(){
        System.out.println("子类独有的方法");
    }
    
    
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

那么想要直接访问子类独有的方法,该如何解决呢,由此引出了转换

二、自动类型转换

2.1 为什么需要引用类型转换

2.1 为什么需要自动类型转换
上面的例子说明了,在多态情况下,使用Animal引用类型构建出来的对象dog无法访问子类Dog所独有的方法walk();强行调用时方法会爆红,编译出错,即我们所说的编译看左边,运行看右边
而且我们在多态情况下调用方法时,首先会检查等式左边的引用类型(父类)中是否有该方法存在,如果父类中没有该方法,则编译器直接报错,也就代表着,父类无法调用子类独有的方法
既然编译都出错了,更别说运行了,这也是多态所造成的,因此如果我们想要调用子类的方法,必须做到向下转型

2.2 向上转型(自动转换)

先了解什么向上转型(儿子变父亲)

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

父类引用指向子类对象

Animal dog  = new Dog()

左边的Animal是引用类型,而dog是由右边的Dog实例对象new出来的,在上面这个等式中,左边的引用Animal指向了子类的对象dog,原本是子类对象的dog完成了向上转型

 对于父类和子类的关系,直接用图来描述

 Animal父类是大范围的类型,而Cat和Dog类均属于动物类的子类,所以对于子类这种范围小的,我们可以自动转型给父类的变量,儿子向上转型,父亲是唯一的,因此是自动转换 

使用格式: 

父类类型  变量名  = new 子类类型();

Animal       dog     = new  Dog()

通过由实例变量Dog类new出来的变量dog作为中介,使得引用变量Animal有所指向,从而完成了向上转型

相当于是

Animal       dog     = (Animal) new  Dog()

因为父类dog   引用了子类对象 所以传参的时候传dog   就相当于传的是子类对象

2.3 向下转型(父亲变儿子,需要强制转换)

向上转型是一个子类变成父类的过程,下面介绍向下转型

 

向下转型是父类向子类转换的过程,这个过程需要强制转换(父亲变儿子肯定是需要条件的),一个可以将父类对象转换为子类对象,可以使用强制类型转换的格式,这便是向下转型

继续拿图说话

 

对于Dog、Cat这些子类来说,他们只是父类Animal的一部分,而对于父类来说。他拥有更多的子类 牛、羊等所以一旦父类要转换成子类,就必须指定要变成哪个子类,必须有指向性,所以向下转型才是强制转换 

使用格式:

向上转型

父类类型  变量名  = new 子类类型();

Animal       dog     = new  Dog()

向下转型

子类类型 子类变量名 = (子类类型) 父类变量名

Dog dog1 = (Dog) dog;   其实就是把父类的类型Animal  强制为子类的类型Dog  

dog1.walk; //此时可以使用子类独有的方法了

代码示例如下 

父类Animal:

//父类
public class Animal {
 
    public void eat() {
        System.out.println("午餐吃狗粮");
    }
 
}

子类Dog:包含有子类独有的方法walk() 

//子类
public class Dog extends Animal {
 
 
    public void walk(){
        System.out.println("子类独有的方法");
    }
 
 
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

通过

Dog dog1 = (Dog) dog;完成向下转型

再利用向下转型成功的子类对象dog1调用子类中独有的方法walk()

 
public class DemoApplication {
 
    public static void main(String[] args) {
 
        //父类类型 对象 = new 子类类型()
        Animal dog = new Dog();
        //向下转型
        //子类类型 子类变量名 = (子类类型) 父类变量名
        Dog dog1 = (Dog) dog;
 
        dog.eat();  //访问的是子类中重写的方法
 
        //通过向下转型的子类对象调用子类独有的方法
        dog1.walk(); 
 
 
    }
}

 

所以对于多态中,无法使用子类特有的方法也通过向下转型,将父类类型强制转换为某个子类类型后,再进行方法的调用

2.4 向下转型的问题

虽然可以通过向下转型可以调用子类独有的方法,但也会产生下面的问题

增加一个子类Cat类,

Cat类中有其独有的方法sleep()

 
//Cat类通过extends关键字继承父类Animal
public class Cat extends Animal {
 
 
 
    public void sleep(){
        System.out.println("Cat类独有的方法");
    }
 
    @Override
    public void eat() {
        System.out.println("晚餐吃猫粮");
    }
}

父类Animal:

//父类
public class Animal {
 
    public void eat() {
        System.out.println("午餐吃狗粮");
    }
 
}

子类Dog类:

//子类
public class Dog extends Animal {
 
 
    public void walk(){
        System.out.println("Dog类独有的方法");
    }
 
 
    @Override
    public void eat() {
        System.out.println("晚餐吃狗粮");
    }
}

启动项:

public class DemoApplication {
 
    public static void main(String[] args) {
 
        //向上转型
        //父类类型 对象 = new 子类类型()
        Animal cat = new Cat();
        //向下转型
        //子类类型 子类变量名 = (子类类型) 父类变量名
        Dog dog1 = (Dog) cat;
 
 
        //通过向下转型的子类对象调用子类独有的方法
        dog1.walk();  
    }
}

 分析:为什么会爆出类型转换异常

因为 在启动项中,向上转型的过程,Animal cat = new Cat(); cat对象是由子类Cat构造出来的
而向下转型的过程中 Dog dog1 = (Dog) cat; 却将其变成了Dog 类的对象,
Dog类和Cat类都是Animal类的儿子类 ,
上面的步骤中的第二步将子类Cat的对象cat变成了兄弟类的对象dog,这就不是向下转型了,因此会报类型转换异常

那么如何避免这种异常呢 ,就需要使用instanceof关键字 

2.5 instanceof关键字详解

Java为我们提供了一个关键字instanceof,它可以帮助我们避免出现ClassCastException样的异常,

 格式:

变量名  instanceof   数据类型

解释:

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

直接拿启动项来进行说明,

代码示例如下:

public class Test {
    public static void main(String[] args) {
        //创建对象
        Animal a = new Dog();
        //编译看左边,运行看右边
        a.eat();//打印的是狗在啃骨头

        //多态的弊端
        //不能调用子类的特有功能
        //当调用成员的方法的时候,编译看左边,运行看右边
        //那么编译的时候首先会检查左边的父类有没有这个方法,如果没有直接报错

        //解决方法
        //变回子类类型即可
        //比如int a=10,把a转换为byte 强制即可
        //int b=(byte)a;
        //Dog d=(Dog)a
        //转换的时候不能瞎转,如果转成其他类的类型,就会报错
        /*if (a instanceof Dog) {
            Dog d = (Dog) a;
            d.lookhome();

        } else if (a instanceof cat) {
            cat c = (cat) a;
            c.sleep();
        } else {
            System.out.println("没有这个类型,无法转换");
        }

*/
        //新特性
        //先判断a是否为Dog类型,如果是,则强转为Dog类型,转换之后的变量名为d
        //如果不是,则不强制,结果直接是false
        if (a instanceof Dog d) {
            d.lookhome();

        } else if (a instanceof cat c) {
            c.sleep();
        } else {
            System.out.println("没有这个类型,无法转换");
        }
    }
}

三、练习

根据需求完成代码:
    1.定义狗类
        属性:
            年龄,颜色
        行为:
            eat(String something)(something表示吃的东西)
            看家lookHome方法(无参数)

    2.定义猫类
        属性:
            年龄,颜色
        行为:
            eat(String something)方法(something表示吃的东西)
            逮老鼠catchMouse方法(无参数)

    3.定义Person类//饲养员
        属性:
            姓名,年龄
        行为:
            keepPet(Dog dog,String something)方法
                功能:喂养宠物狗,something表示喂养的东西
        行为:
            keepPet(Cat cat,String something)方法
                功能:喂养宠物猫,something表示喂养的东西
        生成空参有参构造,set和get方法  
    4.定义测试类(完成以下打印效果):
        keepPet(Dog dog,String somethind)方法打印内容如下:
            年龄为30岁的老王养了一只黑颜色的2岁的狗
            2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
        keepPet(Cat cat,String somethind)方法打印内容如下:
            年龄为25岁的老李养了一只灰颜色的3岁的猫
            3岁的灰颜色的猫眯着眼睛侧着头吃鱼
    5.思考:        
        1.Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?
        2.Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?

父类 Animal

public class Animal {
    private int age;      // age = 20
    private String color; // color = red

    public Animal() {
    }

    public Animal(int age, String color) {
        this.age = age;    // this.age = 20
        this.color = color; // this.color = red
    }


    public int getAge() {
        return age;  // 20
    }


    public void setAge(int age) {
        this.age = age;
    }


    public String getColor() {
        return color; // red
    }


    public void setColor(String color) {
        this.color = color;
    }

    //eat(String something)(something表示吃的东西)
    public void eat(String something) {
        System.out.println("动物在吃" + something);

    }
}

子类Cat 

package itheima.a03polymorphismdemo3;

public class Cat extends Animal {

    public Cat() {
    }

    public Cat(int age, String color) {
        super(age, color);
    }
    public void catchMouse(){
        System.out.println("猫在逮老鼠");
    }
//3岁的灰颜色的猫眯着眼睛侧着头吃鱼
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头" + something );
    }

}

子类Dog

public class Dog extends Animal {  // 继承后获得父类的公共权限的方法和变量
    public Dog() {
    }

    public Dog(int age, String color) {
        super(age, color);  //这里调用了父类的有参构造方法  我赋值   age  20  color red
    }

    public void lookHome() {
        System.out.println("狗在看家");
    }

    //2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
    }
}

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    public int getAge() {
        return age;
    }


    public void setAge(int age) {
        this.age = age;
    }

    /*
    4.定义测试类(完成以下打印效果):
         keepPet(Dog dog,String somethind)方法打印内容如下:
             年龄为30岁的老王养了一只黑颜色的2岁的狗
             2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
         keepPet(Cat cat,String somethind)方法打印内容如下:
             年龄为25岁的老李养了一只灰颜色的3岁的猫
             3岁的灰颜色的猫眯着眼睛侧着头吃鱼
     */
    //使用多态实参传小的,小-到自动转换
    //想到了一个方法,能接受所有的动物,包括猫,狗
    //方法的形参,可以是写这些类的父类,Animal
    public void keepPet(Animal a, String something) {
        if (a instanceof Dog d) {
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "的狗");
            a.eat(something);
        } else if (a instanceof Cat c) {
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "的猫");
            a.eat(something);
        } else {
            System.out.println("没有这个");
        }
    }
}

测试类Test

package itheima.a03polymorphismdemo3;

public class Test {
    public static void main(String[] args) {
        Person p = new Person("老王",25);
        // 向上转型
        Animal a = new Dog(2, "黑");
        //现在调用的是Dog类的方法
        p.keepPet(a,"骨头");
        Animal b=new Cat(3,"灰");
        //现在调用的是Cat类的方法
        p.keepPet(b,"鱼");
    }
}

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值