对象可以在不同的引用类型之间转换。和基本类型一样,引用类型转换可以是放大转换(编译器自动完成),也可以是需要校正的缩小转换(或许运行时还要检查)。要想理解引用类型的转换,必须理解引用类型组成的层次结构,这个体系叫作类层次结构

    每个 Java 引用类型都扩展其他类型,被扩展的类型是这个类型的超类。类型继承超类的字段和方法,然后定义属于自己的一些额外的字段和方法。在 Java 中,类层次结构的根是一个特殊的类,名为 Object。所有 Java 类都直接或间接地扩展 Object 类。Object 类定义了一些特殊的方法,所有对象都能继承(或覆盖)这些方法。

    简单理解类层次结构之后,我们可以定义引用类型的转换规则了。

    • 对象不能转换成不相关的类型。例如,就算使用校正运算符,Java 编译器也不允许把String 对象转换成 Point 对象。
    • 对象可以转换成超类类型,或者任何祖先类类型。这是放大转换,因此不用校正。例如,String 对象可以赋值给 Object 类型的变量,或者传入期待 Object 类型参数的方法。
    • 对象可以转换成子类类型,但这是缩小转换,需要校正。Java 编译器临时允许执行这种转换,但 Java 解释器在运行时会做检查,确保转换有效。根据程序的逻辑,确认对象的确是子类的实例后才会把对象校正成子类类型。否则,解释器会抛出ClassCastException 异常。例如,如果把一个 String 对象赋值给 Object 类型的变量,那么后面可以校正这个变量的值,再变回 String 类型。
      1. Object o = "string"; // 把String对象放大转换成Object类型
      2. String s = (String) o; // 程序后面再把这个Object对象缩小转换成String类型

    没有执行转换操作,而是直接把对象当成超类的实例。这种行为有时称为里氏替换原则(Liskov substitution principle)

    数组是对象,而且有自己的一套转换规则。首先,任何数组都能放大转换成 Object 对象。带校正的缩小转换能把这个对象转换回数组。下面是一个示例:

    1. // 把数组放大转换成Object对象
    2. Object o = new int[] {1,2,3};
    3. // 程序后面……
    4. int[] a = (int[]) o; // 缩小转换回数组类型

    除了能把数组转换成对象之外,如果两个数组的“基类型”是可以相互转换的引用类型,那么数组还能转换成另一个类型的数组。例如:

    1. // 这是一个字符串数组
    2. String[] strings = new String[] { "hi", "there" };
    3. // 可以放大转换成CharSequence[]类型
    4. // 因为String类型可以放大转换成CharSequence类型
    5. CharSequence[] sequences = strings;
    6. // 缩小转换回String[]类型需要校正
    7. strings = (String[]) sequences;
    8. // 这是一个由字符串数组组成的数组
    9. String[][] s = new String[][] { strings };
    10. // 不能转换成CharSequence[]类型,因为String[]类型
    11. // 不能转换成CharSequence类型:维数不匹配
    12. sequences = s; // 不会编译这行代码
    13. // s可以转换成Object类型或Object[]类型,因为所有数组类型
    14. // (包括String[]和String[][]类型)都能转换成Object类型
    15. Object[] objects = s;

    注意,这些数组转换规则只适用于由对象或数组组成的数组。基本类型的数组不能转换为任何其他数组类型,就算基本基类型之间能相互转换也不行:

    1. // 就算int类型能放大转换成double类型
    2. // 也不能把int[]类型转换成double[]类型
    3. // 这行代码会导致编译出错
    4. double[] data = new int[] {1,2,3};
    5. // 但是,这行代码是合法的,因为int[]类型能转换成Object类型
    6. Object[] objects = new int[][] {{1,2},{3,4}};