2021-03-09

面向对象-多态

1. 引入

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

生活中,比如求面积的功能,圆、矩形、三角形实现起来是不一样的。跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。那么此时就会出现各种子类的类型。

2. 定义

2.1 格式

父类类型 变量名 = 子类对象;

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

class Person{
    
    
	private String name;
	private int age;
	
    Person(String name, int age){
    
    
        this.name = name;
        this.age = age;
    }
    
	public void speak(){
    
    
		System.out.println(name + "说:我今年" + age);
	}
}
class Man extends Person{
    
    
    Man(String name, int age){
    
    
        super(name,age);
    }
}
class Woman extends Person{
    
    
    Woman(String name, int age){
    
    
        super(name,age);
    }
}

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

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

3.多态的应用

3.1多态应用在形参实参

父类类型作为方法形式参数,子类对象为实参。
代码如下:

public class Test01 {
    
    
	public static void main(String[] args) {
    
    
		showAnimalEat(new Dog()); //形参 Animal a,实参new Dog() 
								//实参给形参赋值   Animal a = new Dog()   多态引用
		showAnimalEat(new Cat());//形参 Animal a,实参new Cat() 
								//实参给形参赋值   Animal a = new Cat()   多态引用
	}
	
	/*
	 * 设计一个方法,可以查看所有动物的吃的行为
	 * 关注的是所有动物的共同特征:eat()
	 * 所以形参,设计为父类的类型
	 * 	此时不关注子类特有的方法
	 */
	public static void showAnimalEat (Animal a){
    
    
        a.eat();
//        a.catchMouse();//错误,因为a现在编译时类型是Animal,只能看到父类中有的方法
    }

}

3.2 多态应用在数组

数组元素类型声明为父类类型,实际存储的是子类对象

public class Test02 {
    
    
	public static void main(String[] args) {
    
    
		/*
		 * 声明一个数组,可以装各种动物的对象,看它们吃东西的样子
		 */
		Animal[] arr = new Animal[2]; //此时不是new Animal的对象,而是new Animal[]的数组对象
									//在堆中开辟了长度为5的数组空间,用来装Animal或它子类对象的地址
		arr[0] = new Cat();//多态引用   左边arr[0] 是Animal类型,右边是new Cat()
							//把Cat对象,赋值给Animal类型的变量
		arr[1] = new Dog();
		
		for (int i = 0; i < arr.length; i++) {
    
    
			arr[i].eat();
//			arr[i].catchMouse();错误,因为arr[i]现在编译时类型是Animal,只能看到父类中有的方法
		}
	}
}

3.3 多态应用在返回值

方法的返回值类型声明为父类的类型,实际返回值是子类对象

public class Test03 {
    
    
	public static void main(String[] args) {
    
    
		Animal c = buy("猫咪");
		System.out.println(c.getClass());
		c.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;
    }
}

4. 向上转型与向下转型

首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。这个和基本数据类型的转换是不同的。

但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

class Animal {
    
      
    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("吃骨头");  
    }  
    public void watchHouse() {
    
      
        System.out.println("看家");  
    }  
}

class Test{
    
    
    public static void main(String[] args){
    
    
        Cat a = new Cat();//a编译时类型是Cat
        Animal b = a;//b编译时类型是Animal
        Object c = a;//c编译时类型是Object
        
        //运行时类型
        System.out.println(a.getClass());
        System.out.println(b.getClass());
        System.out.println(c.getClass());
        //以上输出都一样,都是Cat类型
        
       	//a,b,c的编译时类型不同
    	//通过a能调用Cat中所有方法,包括从父类继承的,包括自己扩展的
    	//通过b只能调用Animal类及它的父类有的方法,不能调用Cat扩展的方法
    	//通过c只能调用Object类才有的方法
    }
}

为什么要类型转换呢?

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。

  • 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
    • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,一定是安全的,而且也是自动完成的
  • 向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型
    • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,不一定是安全的,需要使用(类型)进行强制类型转换

不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断

变量名/对象 instanceof 数据类型 

猜你喜欢

转载自blog.csdn.net/qq_37698495/article/details/114604453