在 Java 的继承关系图中,我们习惯把父类放在子类的上方,于是我们称将父类型变量强制转换为子类型这一动作为向下转型。
首先我们来看这一段代码:
public class Parent {
}
public class Son extends Parent{
}
public class Main {
public static void main(String[] args) {
Parent p = new Parent();
Son s = (Son)p;
}
}
这段代码编译能通过,但是运行后会抛出运行时异常 ClassCastException
jdk1.8 对于 ClassCastException 的简介:Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.
让我们回顾一下继承:子类通过继承父类,可以继承其成员(字段,方法等),并可对这些成员进行扩展(重写方法等)。
我们可以简单理解为子类是父类的一个超集,对于同一个父类,不同子类对其实现也不同。那么这里的异常便顺理成章:Parent p = new Parent()这一句代码定义了一个父类变量并且引用了一个父类对象,下一行代码,Son s = (Son)p我们无法将父类对象强制转换为其子类,相当于你无法凭空让一个集合变成它的超集。
那么我们将 main 方法中的代码稍作修改:
public class Main {
public static void main(String[] args) {
Parent p = new Son();
Son s = (Son)p;
}
}
因为变量 p 引用了子类对象,在下一行代码中将其向下转型为 Son 类型,便不会抛出异常。因为变量 p 本身引用的就是 Son 类型的对象,只不过在定义时将其赋值给了父类变量(多态)。
举个例子,在堆中 new 出来的子类new Son(),先让他戴上了一个父类的帽子,将子类的个性(子类对于父类的扩展)暂时隐藏。直到需要用到子类个性的地方(比如,需要调用子类特有的方法),再将帽子摘掉,这个动作便是向下转型,使得子类对象恢复了其个性。
多态写法的好处有很多,这里简单概括就是,对于父类定义的方法,不同的子类有不同的实现,于是我们通过将子类对象赋值给父类变量,在对变量进行方法调用时,不同的对象就会展现出不同的行为。
多态的写法应用非常广泛,举个例子,在集合体系中,Collecion 接口的一些常用实现类都有一个参数为 Collection 的构造方法:
ArrayList:
LinkedList:
HashSet:
为何要将 Collection 类型作为参数传递给这些类进行构造?List 实现类的构造方法传参不应该也为 List 吗?实际上 Set 实现类也能传给 List 进行构造,反之亦然,只不过需要遵守各自的特点,有重复元素的 List 传给 Set 进行构造,Set 会进行去重。所以 Collection 作为集合体系的顶层接口,为 List 和 Set 的各种实现类提供最大的通用性,不管是想通过 List 或者 Set 对集合进行构造,通通塞进一个 Collection 就可以,这便是多态的一个应用,为不同的实现类提供一个通用性的变量类型用于接收。将子类的个性暂时隐藏,引用给一个父类的变量,便于对这些对象进行传递,管理。
总结
向下转型就是将父类型变量强制转换为子类型这一动作,其实就是恢复了子类对象的个性:
而 ClassCastException,在尝试向下转型时,实际对象new Parent()根本不是要转型的目的类型(Son),便抛出