JavaSE——面向对象7:三大特征之多态(向上转型、向下转型、instanceof关键字、动态绑定机制、多态数组和参数)

目录

一、多态的引入

二、多态的基本介绍

(一)方法的多态——重载和重写

(二)对象的多态(多态的核心)

三、多态的快速入门

四、向上转型

五、向下转型 

六、属性不存在重写,只看编译类型

(一)案例1

(二)案例2

七、instanceof关键字

八、Java的动态绑定机制(重要)

九、多态的应用

(一)多态数组

(二)多态参数


一、多态的引入

        编写一个程序,实现主人喂食:有个主人类,给不同的动物喂食不同的食物。我们前面学完了封装和继承,利用之前的知识体系,要实现这个需求,可以这么做:

        创建不同的动物类和食物类,所有的动物可以继承父类Animal,所有的食物类可以继承父类Food。每次喂食的方式与对象不同,要写很多喂食方法,如下图。可见,图中的多个feed方法实现了方法的重载,代码看起来冗余,复用性不高,难以维护和管理。

        那么,能不能只用写一个feed方法,就可以实现不同的喂食效果呢?即给不同的动物喂食不同的食物。因此,我们就需要多态来解决这个问题。

继承体系:

                                                

 

二、多态的基本介绍

        多态是指方法对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

(一)方法的多态——重载和重写

public class PloyMethod {
    public static void main(String[] args) {
        //方法重载体现多态
        A a = new A();
        //这里我们传入不同的参数,就会调用不同sum方法,就体现多态
        System.out.println(a.sum(10, 20)); // 30
        System.out.println(a.sum(10, 20, 30)); // 60

        //方法重写体现多态
        B b = new B();
        a.say(); // A say() 方法被调用...
        b.say(); // B say() 方法被调用...
    }
}

class B { // 父类
    public void say() {
        System.out.println("B say() 方法被调用...");
    }
}

class A extends B { // 子类

    public int sum(int n1, int n2) {//和下面sum 构成重载
        return n1 + n2;
    }

    public int sum(int n1, int n2, int n3) {
        return n1 + n2 + n3;
    }

    @Override
    public void say() { // 子类重写父类方法
        System.out.println("A say() 方法被调用...");
    }
}

(二)对象的多态(多态的核心)

  1. 一个对象的编译类型和运行类型可以不一致。即父类的引用指向子类的对象。例如:Animal animal = new Dog();   [animal 编译类型是Animal,运行类型是Dog,Dog类继承了Animal类]。
  2. 编译类型在定义对象时,就确定了,不能改变。
  3. 运行类型是可以变化的。例如上面的animal可以指向Cat类:animal = new Cat();  [animal的运行类型变成了Cat,编译类型仍然是Animal]
  4. 编译看(=号)左边,运行看(=号)右边。

代码示例:

public class Animal { // 父类
    public void cry() {
        System.out.println("Animal cry() 动物在叫....");
    }
}


public class Cat extends Animal { // 子类
    @Override
    public void cry() {
        System.out.println("Cat cry() 小猫喵喵叫...");
    }
}


public class Dog extends Animal { // 子类
    @Override
    public void cry() {
        System.out.println("Dog cry() 小狗汪汪叫...");
    }
}


public class PolyObject {
    public static void main(String[] args) {
        // 对象的多态

        // 编译类型:Animal      运行类型:Dog
        Animal animal = new Dog();
        // 因为运行到这里时,animal的运行类型是Dog,所以cry方法时Dog的cry
        animal.cry(); // Dog cry() 小狗汪汪叫...

        // 编译类型:Animal      运行类型:Cat
        animal = new Cat();
        animal.cry(); // Cat cry() 小猫喵喵叫...
    }
}

三、多态的快速入门

        现在我们可以使用多态机制,可以统一管理主人喂食的问题:只需要改写Master类中的feed方法即可。

        原有main方法中的代码不必更改,实现了和原来一样的效果,再创建更多的动物和食物也可以直接调用feed方法。 

 

四、向上转型

多态的使用前提:两个对象(类)存在继承关系。

多态的向上转型:

本质:父类的引用指向子类的对象

语法:父类类型 引用名 = new 子类类型;

特点:编译看左边,运行看右边。

可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员,最终运行效果看子类的具体实现!

向上转型调用方法的规则如下:
(1)可以调用父类中的所有成员(需遵守访问权限)
(2)但是不能调用子类的特有的成员
(3)在编译阶段,能调用哪些成员,是由编译类型来决定的
(4)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与继承部分方法调用规则一致。

代码示例:

编写父类Animal:

public class Animal {
    String name = "动物";
    int age = 10;

    public void sleep() {
        System.out.println("睡");
    }

    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }

    public void show() {
        System.out.println("hello,你好");
    }
}

编写子类Cat,继承父类Animal:

public class Cat extends Animal {
    @Override
    public void eat() { // 方法重写
        System.out.println("猫吃鱼");
    }

    public void catchMouse() { // Cat特有方法
        System.out.println("猫抓老鼠");
    }
}

main方法实现多态的向上转型: 

public class PolyDetail {
    public static void main(String[] args) {

        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        Object obj = new Cat();//这样写是可以的,因为 Object 也是 Cat的父类

        // animal.catchMouse();错误 不能调用子类特有成员

        // 编译时父类有成员,且有权限,可以调用
        // 运行时的效果看子类,子类如果没有成员,再看父类,调用父类的成员
        animal.eat();//猫吃鱼..
        animal.run();//跑
        animal.show();//hello,你好
        animal.sleep();//睡
    }
}

五、向下转型 

我们知道,向上转型只能调用父类中的方法,但是如果想要调用子类中的方法,就需要向下转型。

多态的向下转型:

1)语法:子类类型 引用名 = (子类类型)父类引用;

2)只能强转父类的引用,不能强转父类的对象

3)要求父类的引用,必须指向的是当前目标类型的对象

4)可以调用子类类型中所有的成员

还是上面的代码,如果想要调用Cat的 catchMouse方法,实现方法如下:

PolyDetail类:

public class PolyDetail {
    public static void main(String[] args) {   
        //多态的向下转型

        //(1)语法:子类类型 引用名 =(子类类型)父类引用;
        // cat 的编译类型 Cat,运行类型是 Cat
        Animal animal = new Cat();
        Cat cat = (Cat) animal;
        cat.catchMouse(); // 猫抓老鼠

        //(2)要求父类的引用,必须指向的是当前目标类型的对象
        // 下面这样写运行会报ClassCastException,因为animal原来是指向Cat的
        Dog dog = (Dog) animal;
        // animal原本是指向Cat的,现在向下转型指向了Dog是不合理的,因为Cat与Dog之间没有关系
    }
}

六、属性不存在重写,只看编译类型

(一)案例1

public class PolyDetail02 {
    public static void main(String[] args) {
        // 属性没有重写之说!属性的值看编译类型
        Base base = new Sub(); // 向上转型
        System.out.println(base.count);// 看编译类型 10

        Sub sub = new Sub();
        System.out.println(sub.count);// 看编译类型 20
    }
}

class Base { // 父类
    int count = 10;// 属性
}

class Sub extends Base {// 子类
    int count = 20;// 属性
}

(二)案例2

public class PolyExercise02 {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.count);//20
        s.display();//20
        Base b = s;
        System.out.println(b == s);//T
        System.out.println(b.count);//10 属性没有重写一说!!!
        b.display();//20
    }
}

class Base { // 父类
    int count = 10;

    public void display() {
        System.out.println(this.count);
    }
}

class Sub extends Base { // 子类
    int count = 20;

    public void display() {
        System.out.println(this.count);
    }
}

七、instanceof关键字

        instanceof是比较操作符,用于判断对象的运行类型是否为xx类型,或者是否为xx类型的子类型。

代码解读:

public class PolyDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof BB);// true
        System.out.println(bb instanceof AA);// true

        //aa 编译类型是AA, 运行类型是BB
        //BB是AA的子类
        AA aa = new BB();
        System.out.println(aa instanceof AA);
        System.out.println(aa instanceof BB);

        Object obj = new Object();
        System.out.println(obj instanceof AA);//false
        String str = "hello";
        //System.out.println(str instanceof AA);
        System.out.println(str instanceof Object);//true
    }
}

class AA {
} // 父类

class BB extends AA {
}// 子类

八、Java的动态绑定机制(重要)

class A {//父类
    public int i = 10;
    //动态绑定机制:

    //父类sum()
    public int sum() {return getI() + 10;}

    //父类sum1()
    public int sum1() {return i + 10;}

    //父类getI
    public int getI() {return i;}
}

class B extends A {//子类
    public int i = 20;

    public int sum() {return i + 20;}

    //子类getI()
    public int getI() {return i;}

    public int sum1() {return i + 10;}
}
public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());
        System.out.println(a.sum1());
    }
}

调用a.sum()时,实际调用的是B类中的sum方法,所以输出40;

调用a.sum1()时,实际调用的也是B类中的sum1方法,所以输出30;


        当我们把B类中的sum()和sum1()方法注释掉以后,结果是什么?

思路解析:

        刚开始调用a.sum(),实际仍然是去B类中查找sum()方法,发现B类中没有sum()方法,所以就去B类的父类,也就是A类中查找;

        在A类中找到了sum()方法,但是return getI() + 10;里面又调用一个getI()方法,注意!此时就会再次回到B类中查找是否有getI()方法!在B类中找到getI(),返回的是B类中的属性i的值,为20,所以getI()=20;

        再次回到return getI() + 10;这句代码,已知getI()=20,那么a.sum()返回值就是20+10=30;


        再来看a.sum1()方法,它与上面的一样:先去B类中查找是否有sum1()方法,没有找到,又去A类中查找sum1()方法,在A类中找到了,进入方法,return i + 10; 这里的i是A类的i属性的值,也就是10;所以,return i + 10=10+10=20,所以a.sum1()=20。

class A {//父类
    public int i = 10;
    //动态绑定机制:

    //父类sum()
    public int sum() {return getI() + 10;}

    //父类sum1()
    public int sum1() {return i + 10;}

    //父类getI
    public int getI() {return i;}
}

class B extends A {//子类
    public int i = 20;

  //  public int sum() {return i + 20;}

    //子类getI()
    public int getI() {return i;}

   // public int sum1() {return i + 10;}
}

九、多态的应用

(一)多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

应用实例:现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个Teacher对象, 统一放在数组中,并调用每个对象say方法。

Person类:

Student类:

Teacher类:

遍历Persons数组,调用say方法:

public class PloyArray {
    public static void main(String[] args) {
       
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        // 循环遍历多态数组,调用say
        // person[i] 编译类型是 Person ,运行类型是是根据实际情况有JVM来判断
        for (int i = 0; i < persons.length; i++) {
            System.out.println(persons[i].say());
        }      
    }
}

运行结果:

 


应用实例升级:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study,如何调用?

给Teacher类添加teach方法:

public void teach() {
    System.out.println("老师 " + getName() + " 正在讲java课程...");
}

个体Student类添加study方法:

public void study() {
    System.out.println("学生 " + getName() + " 正在学习java课程...");
}

修改main方法中的内容: 

public class PloyArray {
    public static void main(String[] args) {        
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        // 循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            System.out.println(persons[i].say());
            // persons[i].teach(); 错误,无法调用

            // 使用类型判断+向下转型 就能调用子类特有的方法
            if (persons[i] instanceof Student) {
//                Student student = (Student) persons[i];
//                student.study();
                // 或者
                ((Student) persons[i]).study();
            } else if (persons[i] instanceof Teacher) {
                ((Teacher) persons[i]).teach();
            } else if (persons[i] instanceof Person) {
                // 不做任何处理
            } else { // 三种类型都不是再输出
                System.out.println("你的类型有误,请检查...");
            }
        }       
    }
}

运行结果:

 

(二)多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型

应用实例1:文章开篇的主人喂食动物

应用实例2:

        定义员工类Empolyee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。

        测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]。

        测试了中添加一个方法testWork,如果是普通员工,则调用work方法;如果是经理,则调用manage方法。

代码实现:

Employee类:

public class Employee {
    private String name;
    private double salary;

    // 计算年工资
    public double getAnnual() {
        return salary * 12;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

普通员工Worker类:

public class Worker extends Employee {

    public Worker(String name, double salary) {
        super(name, salary);
    }

    @Override
    public double getAnnual() {
        return super.getAnnual();
    }

    public void work() {
        System.out.println("普通员工" + getName() + "在工作...");
    }
}

经理Manager类:

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary) {
        super(name, salary);
    }

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }

    public void manager() {
        System.out.println("经理" + getName() + "在管理员工...");
    }
}

测试类PolyParameter:

public class PolyParameter {
    public static void main(String[] args) {
        Worker zhangsan = new Worker("zhangsan", 5000);

        Manager lisi = new Manager("lisi", 15000, 6000);
        PolyParameter polyParameter = new PolyParameter();
        polyParameter.showEmpAnnual(zhangsan); // 60000.0
        polyParameter.showEmpAnnual(lisi); // 186000.0

        polyParameter.testWork(zhangsan); // 普通员工zhangsan在工作...
        polyParameter.testWork(lisi); // 经理lisi在管理员工...
    }

    public void showEmpAnnual(Employee e) {
        // 这里有动态绑定机制,不需要用instanceof判断
        System.out.println(e.getAnnual());
    }

    public void testWork(Employee e) {
        if (e instanceof Worker) {
            // 调用子类特有方法,需要向下转型
            ((Worker) e).work();
        } else if (e instanceof Manager) {
            // 调用子类特有方法,需要向下转型
            ((Manager) e).manager();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值