在介绍多态的概念前,我们先介绍一下向上转型作为引子。
一、向上转型一个Person类型的变量,可以指向一个Student类型的实例:
Person p = new Student();
这是因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!
这种把一个子类类型安全地变为父类甚至祖宗类类型的赋值,被称为向上转型(upcasting)。
向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
Student s = new Student(); Person p = s; // upcasting, ok Object o1 = p; // upcasting, ok Object o2 = s; // upcasting, ok
注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object。
二、多态多态(Polymorphic)多态指的是同一个行为具有多个不同表现形式。也就是说,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。
如何实现多态?多态的实现具有三种必要条件:
- 继承
- 重写父类方法
- 父类引用指向子类对象
比如下面这段代码:
class Fruit {
public void eat() {
System.out.println("eat Fruits");
}
}
class Apple extends Fruit { // 1.继承
@Override
public void eat() { // 2.重写父类方法
System.out.println("eat Apples");
}
public static void main(String[] args) {
Fruit fruit = new Apple(); // 3.父类引用指向子类对象
fruit.eat(); // eat Apples
}
}
执行main方法后,输出结果为"eat Apples",也就是说,实际调用的是Apple子类重写后的eat方法。
注意:对象的多态性,只适用于方法,不适用于属性。也就是说,即使子类对象修改了字段属性,父类对象调用时也看不见这个修改。
功能扩展多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
我们举个例子:
class Driver{
// conn = new MySQlConnection();
// conn = new OracleConnection();
public void doData(Connection conn){
//按照规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
我们定义了一个操作数据库的类,将来它就可以根据具体传入的Connection对象的不同,操作操作不同数据库的数据。
- 假设传入的是继承Connection类的MySQLConnection类的对象,那么它就可以去操作MySQL数据库中的数据。
我们可以看出,此时这个Connection类仅仅只是定义了一个“规范”,使用这个Connection类时实际使用的都是子类对象重写过的方法。所以多态常常会与抽象类/接口一起使用,因为这个父类本身往往并不需要有具体的方法实现。
- 事实上,Java本身提供的Connection类确实是一个接口,具体使用时,都要导入各个数据库厂商实现过的数据库API。
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用(强行调用会报错)。如何才能调用子类特的属性和方法?我们需要使用向下转型。
(二)向下转型概念和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
我们使用强制类型转换符()来实现向下转型。
(三)使用时的注意点只有父类变量指向子类对象时,向下转型可以成功。例如:
Person p1 = new Student(); // upcasting, ok Person p2 = new Person(); Student s1 = (Student) p1; // ok Student s2 = (Student) p2; // runtime error! ClassCastException!
-
把p1转型为Student会成功,因为p1确实指向Student实例。
-
把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
失败的时候,Java虚拟机会报ClassCastException。
(四)如何避免出错 1. instanceof的概念为了避免向下转型出错,Java提供了instanceof操作符,用于判断一个实例究竟是不是指定类型或这个类型的子类:
Person p = new Person(); System.out.println(p instanceof Person); // true System.out.println(p instanceof Student); // false Student s = new Student(); System.out.println(s instanceof Person); // true System.out.println(s instanceof Student); // true Student n = null; System.out.println(n instanceof Student); // false
为何instanceof对于指定类型的子类的对象也会判定为true呢?这其实也很好理解,因为不管你是一个学生还是一位教师,你都是一个人,要是有人说你不是人,你怕是会跳过去打他吧。所以instanceof也是同样道理。
如果一个引用变量为null,那么对任何instanceof的判断都为false。
2. instanceof的使用利用instanceof,在向下转型前可以先判断:
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:
Object obj = "hello";
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
可以改写如下:
Object obj = "hello";
if (obj instanceof String s) {
// 可以直接使用变量s:
System.out.println(s.toUpperCase());
}
这种使用instanceof的写法更加简洁。
(五)图示类比


