对象可以在不同的引用类型之间转换。和基本类型一样,引用类型转换可以是放大转换(编译器自动完成),也可以是需要校正的缩小转换(或许运行时还要检查)。要想理解引用类型的转换,必须理解引用类型组成的层次结构,这个体系叫作类层次结构。
每个 Java 引用类型都扩展其他类型,被扩展的类型是这个类型的超类。类型继承超类的字段和方法,然后定义属于自己的一些额外的字段和方法。在 Java 中,类层次结构的根是一个特殊的类,名为 Object。所有 Java 类都直接或间接地扩展 Object 类。Object 类定义了一些特殊的方法,所有对象都能继承(或覆盖)这些方法。
简单理解类层次结构之后,我们可以定义引用类型的转换规则了。
- 对象不能转换成不相关的类型。例如,就算使用校正运算符,Java 编译器也不允许把
String对象转换成Point对象。 - 对象可以转换成超类类型,或者任何祖先类类型。这是放大转换,因此不用校正。例如,
String对象可以赋值给Object类型的变量,或者传入期待Object类型参数的方法。 - 对象可以转换成子类类型,但这是缩小转换,需要校正。Java 编译器临时允许执行这种转换,但 Java 解释器在运行时会做检查,确保转换有效。根据程序的逻辑,确认对象的确是子类的实例后才会把对象校正成子类类型。否则,解释器会抛出
ClassCastException异常。例如,如果把一个String对象赋值给Object类型的变量,那么后面可以校正这个变量的值,再变回String类型。Object o = "string"; // 把String对象放大转换成Object类型String s = (String) o; // 程序后面再把这个Object对象缩小转换成String类型
没有执行转换操作,而是直接把对象当成超类的实例。这种行为有时称为里氏替换原则(Liskov substitution principle)
数组是对象,而且有自己的一套转换规则。首先,任何数组都能放大转换成 Object 对象。带校正的缩小转换能把这个对象转换回数组。下面是一个示例:
// 把数组放大转换成Object对象Object o = new int[] {1,2,3};// 程序后面……int[] a = (int[]) o; // 缩小转换回数组类型
除了能把数组转换成对象之外,如果两个数组的“基类型”是可以相互转换的引用类型,那么数组还能转换成另一个类型的数组。例如:
// 这是一个字符串数组String[] strings = new String[] { "hi", "there" };// 可以放大转换成CharSequence[]类型// 因为String类型可以放大转换成CharSequence类型CharSequence[] sequences = strings;// 缩小转换回String[]类型需要校正strings = (String[]) sequences;// 这是一个由字符串数组组成的数组String[][] s = new String[][] { strings };// 不能转换成CharSequence[]类型,因为String[]类型// 不能转换成CharSequence类型:维数不匹配sequences = s; // 不会编译这行代码// s可以转换成Object类型或Object[]类型,因为所有数组类型// (包括String[]和String[][]类型)都能转换成Object类型Object[] objects = s;
注意,这些数组转换规则只适用于由对象或数组组成的数组。基本类型的数组不能转换为任何其他数组类型,就算基本基类型之间能相互转换也不行:
// 就算int类型能放大转换成double类型// 也不能把int[]类型转换成double[]类型// 这行代码会导致编译出错double[] data = new int[] {1,2,3};// 但是,这行代码是合法的,因为int[]类型能转换成Object类型Object[] objects = new int[][] {{1,2},{3,4}};
