Java基础38 面向对象三大特征之多态



)


多态

● 多【多种】态【状态】

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

1.多态的具体体现

  1. 方法的多态
    重写和重载体现多态

  2. 对象的多态(核心)
    (1)一个对象的编译类型和运行类型可以不一致。
    (2)编译类型在定义对象时,就确定了,不能改变。
    (3)运行类型是可以变化的。
    (4)编译类型看定义时 “=” 号 的左边,运行看 “=” 号的右边

例如:
Animal animal = new Dog();
【这里animal编译类型是Animal,运行类型是Dog】
animal = new Cat();
【animal的运行类型变成了Cat,编译类型仍然是Animal】

2.向上转型

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

多态的向上转型

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

  2. 语法:父类类型 引用名 = new 子类类型();

  3. 特点:

    • 编译类型看左边,运行类型看右边。
    • 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;
    • 最终运行效果看子类的具体实现。

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

以下面例子为例说明向上转型的规则

Animal类

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("表演");
		}	
}

Cat类,继承Animal类

class Cat extends Animal {
		public void eat(){ //重写父类的eat
				System.out.println("猫吃鱼");
		}
		public void catchMouse(){ //cat的特有方法
				System.out.println("猫抓老鼠");
		}
}

Test类

public class Test{
	public static void mian(String[] args){
		//向上转型:父类的引用指向子类的对象
		//语法:父类类型引用名 = new 子类类型();
		Aniaml animal = new Cat();
		// Object obj = new Cat(); 也是可以的,因为Object也是Cat的父类
		
		//调用父类中的所有成员(需遵守访问权限)
		//不能调用子类的特有成员
		//animal.catchMouse(); 是错误的,因为catchMouse是Cat中的特有方法
		animal.eat(); 
		animal.run(); 
		animal.show();
		animal.sleep();
		
		System.out.println("out~");
	}
}

3.向下转型

多态的向下转型

  1. 语法:子类类型 引用名 = (子类类型) 父类引用
  2. 只能强转父类的引用,不能强转父类的对象
  3. 要求父类的引用必须指向的是当前目标类型的对象
  4. 可以调用子类类型中所有的成员

向下转型的使用

以Aniaml类与cat类为例,新增一个dog类

Dog类

public class Dog extends Animal{
}

在Test类中测试

调用Cat的catchMouse方法
//使用多态的向下转型
//(1)语法 : 子类类型 引用名 = (子类类型) 父类引用;
Cat cat = (Cat) animal; //cat的编译类型是Cat,运行类型是Cat
cat.catchMouse();
//(2)父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog)animal; //这句会报错,提示类异常

4.属性重写

属性没有重写之说,属性的值看编译类型。

public class Test{
	public static void main(String[] args){
		//属性没有重写之说,属性的值看编译类型
		Base base = new Sub(); //向上转型
		System.out.println(base.count); //编译类型为base,则输出10
		Sub sub = new Sub();
		System.out.println(base.count); //编译类型为sub,则输出20
		
	}
}

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

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

5.instanceOf

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

public class Test{
	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 = new BB();
		System.out.println(aa instanceof AA); //true
		System.out.println(aa instanceof BB); //true

		Object obj = new Object();
		System.out.println(obj instanceof AA);//false
		System.out.println(AA instanceof obj);//true

		String str = "yes";
		System.out.println(str instanceof Object);//true
	}
}

6.动态绑定机制(核心)

Java重要特征:动态绑定机制

现在有两个类,A类与B类,其中B类是A类的子类,在main方法中创建对象: A a = new B();调用a.sum()与a.sum1()看看分别得到什么结果?

A类

class A{ //父类
	public int i =10;
	public int sum(){
		return getI() + 10;
	}
	public int sum1(){
		return i + 10;
	}
	public int getI(){
		return i;
	}
}

B类

class B extends A{ //子类
	public int i = 20;
	public int sum(){
		return i + 20;
	}
	public int getI(){
		return i;
	}
	public int sum1(){
		return i + 10;
	}
}

在main方法中 A a = new B(); //向上转型 System.out.println(a.sum()); //输出40
System.out.println(a.sum1()); //输出30

现在注释掉B类中的sum方法,那输出的结果又该如何?

B类

class B extends A{ //子类
	public int i = 20;
	//public int sum(){
	//	return i + 20;
	//}
	public int getI(){
		return i;
	}
	public int sum1(){
		return i + 10;
	}
}

在main中调用
System.out.println(a.sum());

因为B类中的sum被注释了,所以会自动向上找sum方法,而父类A中恰好有sum方法,所以调用了A类的sum方法,而A类的sun方法里返回的是getI()+10;而A与B类中都有getI方法,这里就涉及到了java的动态访问机制

● Java的动态访问机制

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。【属性没有动态绑定机制】

现在回过头去看刚才的问题,刚才调用的getI()方法,是属于对象的方法,所以该方法会和该对象的内存地址/运行类型绑定,我们创建的时候使用的运行类型是B类型,所以getI应该调用B类的getI方法:return
i;所以最后a.sum()输出的结果为:20+10 = 30。

那如果再把B类中的sum1()方法再注删掉呢?a.sum1()的结果是什么?

A类

class A{ //父类
	public int i =10;
	public int sum(){
		return getI() + 10;
	}
	public int sum1(){
		return i + 10;
	}
	public int getI(){
		return i;
	}
}

B类

class B extends A{ //子类
	public int i = 20;
	public int getI(){
		return i;
	}
}

由于B中并没有sum1方法,所以依然调用父类A的sum1方法,而sum1()中的 i + 10;i为属性,前面介绍过,java动态绑定机制是:当调用对象的属性时,,没有绑定动态机制,就是哪里声明,哪里使用,所以直接返回B类中的i,结果为:10 + 10 = 20


7.多态数组

多态数组

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

应用实例:

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

代码实现

Person类

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

    public String say(){ //返回名字和年龄
        return name + "\t" + age;
    }

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

Student类

public class Student extends Person{

    private double score;

    //重写父类say
    public String say(){

        return "学生" + super.say() + "score =" + score;
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }


    public double getScore() {

        return score;
    }

    public void setScore(double score) {

        this.score = score;
    }
}

Teacher类

public class Teacher extends Person{
    private double salary;

    //重写父类的say方法
    public String say(){

        return "老师 :" + super.say()+"salary = " + salary;
    }

    public Teacher(String name, int age, double salary) {
        super(name,age);
        this.salary = salary;
    }

    public double getSalary() {

        return salary;
    }

    public void setSalary(double salary) {

        this.salary = salary;
    }
}

Test类(主方法类)

创建多态数组并遍历调用say方法

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    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("queen",29,25000);

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

运行效果

在这里插入图片描述

现在升级需求:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用?

含特有方法的Teacher

public class Teacher extends Person{
    private double salary;

    //重写父类的say方法
    public String say(){

        return "老师 :" + super.say()+"salary = " + salary;
    }

    //teacher特有方法
    public void teach(){
        System.out.println("老师:" +getName() + "正在授课");
    }

    public Teacher(String name, int age, double salary) {
        super(name,age);
        this.salary = salary;
    }

    public double getSalary() {

        return salary;
    }

    public void setSalary(double salary) {

        this.salary = salary;
    }
}

含特有方法的Student

public class Student extends Person{

    private double score;

    //重写父类say
    public String say(){

        return "学生" + super.say() + "score =" + score;
    }

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

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }


    public double getScore() {

        return score;
    }

    public void setScore(double score) {

        this.score = score;
    }
}

Test类

通过向下转型与类型判断完成子类特有方法的调用。

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    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("queen",29,25000);

        //循环遍历多态数组,调用say
        for(int i = 0; i < persons.length; i++){
            //这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
            //使用 类型判断 + 向下转型
            if(persons[i] instanceof Student){ //判断person[i] 的运行类型是不是Student
                Student student = (Student) persons[i];
                student.study();
                //熟悉后可以简化为一条语句:
                //((Student)persons[i]).study();
            } else if(persons[i] instanceof Teacher){
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
                //((Teacher)persons[i]).teach();
            } else if(persons[i] instanceof Person){

            } else{
                System.out.println("类型有误,请检查清楚!");
            }
        }
        }
    }

运行效果

在这里插入图片描述


8.多态参数

多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。

应用案例

  • 定义员工类Employee,包含姓名和月工资【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 12 * salary;
    }

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

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

    public double getSalary() {
        return salary;
    }

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

manage类

public class manger extends Employee{
    private double bonus;

    //获取年薪
    public double getAnnual(){
        return super.getAnnual() + bonus;
    }

    public void manage(){
        System.out.println("经理: "+ getName()+"is working");
    }

    public manger(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;
    }
}

普通员工类(Ordinary_employees)

public class Ordinary_employees extends Employee{

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

    public double getAnnual(){ //因为普通员工没有其他收入,则直接调用父类方法就可以
        return super.getAnnual();
    }

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

test类

创建普通员工与经理的对象,添加两个新方法并输出需求。

public class test {
    //    在main中,分别创建Person和Student对象,调用say方法输出自我介绍
    public static void main(String[] args) {

        Ordinary_employees tom = new Ordinary_employees("tom",2500);
        manger milan = new manger("milan",5000,250000);
        test t = new test();
        t.showEmpAnnual(tom);
        t.showEmpAnnual(milan);
        t.testWork(tom);
        t.testWork(milan);
    }
    //添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual()】
    public void showEmpAnnual(Employee e){
        System.out.println(e.getAnnual());//动态绑定机制
    }
    //添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
    public void testWork(Employee e){
            if(e instanceof Ordinary_employees){
                ((Ordinary_employees)e).work();//向下转型
            }else if(e instanceof  manger){
                ((manger)e).manage(); //向下转型
            } else {

            }
    }

效果实现

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值