面向对象-多态
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 数据类型