多态的引入(polymorphic)
什么是多态?
-
某同一个事物,在不同的时刻表现出不同的状态
-
对于Java而言,指的是同一个类型引用,在指向不同的具体对象时,运行时表现不同的行为
-
其实就是父类引用指向了不同的子类对象
-
例如
-
Animal a = new Cat(); Animal a = new Dog();
-
-
-
多态的前提条件
- 要有继承关系
- 继承后发生子父类的方法重写
- 父类的引用指向子类的对象
-
不能发生多态的情况
- 不能够被继承,final类
- 不能被方法覆盖
- final方法
- static方法
- private方法
- 构造方法
-
当父类的引用,指向不同的实际对象时,调用方法,结果不同
-
写一个方法,参数列表为父类引用,可以传入具体的对象
- 多态对程序的扩展性
多态的访问特点
发生多态后,通过引用调用成员的方式发生了改变
父类 引用 = new 子类();
-
对于成员变量而言
- 编译时看左边,运行时看左边
- 也就是说成员变量是没有“多态”的(因为多态的前提就是发生方法重写)
- 多态现象是发生在方法之间,和成员变量没有关系
-
对于成员方法而言
- 编译时看左边,运行时看右边
- 编译时看左边,运行时看右边
-
编译看左边
- 编译看左边是说通过引用变量可以访问到的子类成员的范围
- 是由引用类型来决定的,也就是说由父类中定义的成员决定
- 我们只能通过引用去访问,堆上的对象,引用中必然装有该类型对象的成员信息
- 只有通过引用变量,我才能访问到堆上的对象
- 也就是说,对象的访问受限于引用变量本身的类型
- 编译看左边是说通过引用变量可以访问到的子类成员的范围
解释方法的多态性,一个很贴切的例子
- 我家中的一台电视机,贼贵,功能很丰富
- 对于电视机而言,我们只能使用,遥控器去操作电视机
- 这也就是说,只有遥控器上提供的功能我们才能使用
- 即便电视机本身功能多么强大,如果遥控器上只有音量键,那我们也毫无办法
- 遥控器有啥功能,决定了我们能使用的功能
- 即使电视机本身功能再丰富,没有遥控器的支持,我们啥也用不了
把这个例子转换到Java程序中
- 电视机就相当于对象本身,而遥控器就是引用变量
- 实际对象的功能再强大,如果引用中没有这个功能,那也无法调用该功能
- 对象的行为,受限于引用变量,和对象本身没有直接关系
- 对象的引用类型决定了可以访问对象的成员范围
- 编译时看左边,运行时看右边
解释成员变量不具有多态性,一个例子
- 成员变量描述的是对象的“外貌特征”
- 把子类对象赋值给父类类型的引用,就相当于给子类对象披上了一个父类类型马甲
- 外貌特征上来看,这时候的子类就变成了父类
- 编译时看左边,运行时看左边
多态的优缺点
-
多态的优点:
- 要实现多态,必须要继承,提高了程序的可维护性(继承保证)
- 发生多态后,同一个引用调用方法产生不同的行为
- 提高了程序的简洁性和扩展性(多态保证)
-
多态的缺点:
- 不能访问子类特有的功能
多态中的转型
基本数据类型之间是可以发生数据类型转换的,引用数据类型也是可以的
但是引用数据类型发生转换的条件比较苛刻,出错后的问题也更严重
引用数据类型要发生类型转换
- 前提:具有父子关系的两个类型之间
- 没有父子关系的两个类型之间不能发生类型转换,通过不了编译
多态中的转型内存图
多态实现猫和狗案例
Animal
public class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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;
}
public void eat() {
System.out.println("动物吃东西");
}
}
Cat
public class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
Dog
public class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
AnimalDemo
/*
测试类
*/
public class AnimalDemo {
public static void main(String[] args) {
//创建猫类对象进行测试
Animal a = new Cat();
a.setName("加菲");
a.setAge(5);
System.out.println(a.getName() + "," + a.getAge());
a.eat();
a = new Cat("加菲", 5);
System.out.println(a.getName() + "," + a.getAge());
a.eat();
}
}
自动类型转换
-
子类的引用转换成父类的引用,在继承链中属于向上,编译器默认允许
-
称之为自动类型转换或者向上转型
-
语法
-
父类 引用名 = new 子类();
-
子类一定可以看成父类,所以能够自动转型
-
向上转型是安全,可以放心使用
-
强制类型转换
-
父类的引用转换成子类的引用,在继承链中属于向下,编译器默认不允许,需要显式强行转换
-
称之为强制类型转换或者向下转型
-
语法
-
子类 引用名 = (子类)父类引用;
-
子类继承和扩展了父类,父类大多数情况下都不能看成子类,所以需要强制类型转换
-
重要前提:强制类型转换若想成功,必须是该父类引用指向的对象本身就是一个要强转的子类对象
-
-
强制类型转换是不安全的,要想转型成功,必须真实的对象和要转型的类型一致
- 父类引用指向的不一定就是那个你要强转的子类的对象
- 比如动物类的引用指向了一个猫对象,现在把引用强转成一个狗引用,能成功吗?
-
为了保障安全,向下转型推荐使用instanceof关键字校验
-
语法
-
引用名 instanceof 类名
-
这个表达式返回一个布尔类型的值
- true代表该引用指向的对象,是一个后面类名的对象
- null instanceof 任何类 结果都是false
-
ClassCastException
强制类型转换一旦失败,就会抛出ClassCastException,程序报错终止
- 没有人会故意给自己找麻烦
- 不到万不得已,不要使用强制类型转换