数组的类型和类一样,也是引用类型。数组的实例和类的实例一样,也是对象。和类不同的是,数组的类型不用定义,只需在元素类型后面加上一对中括号即可。例如,下述代码声明了三种不同类型的数组:
byte b; // byte 是基本类型byte[] arrayOfBytes; // byte[] 是由 byte 类型的值组成的数组byte[][] arrayOfArrayOfBytes; // byte[][] 是由 byte[] 类型的值组成的数组String[] points; // String[] 是由字符串组成的数组
数组的长度不是数组类型的一部分。
数组类型不是类,但数组实例是对象。这意味着,数组从 java.lang.Object 类继承了方法。数组实现了 Cloneable 接口,而且覆盖了 clone() 方法,确保数组始终能被复制,而且 clone() 方法从不抛出 CloneNotSupportedException 异常。数组还实现了 Serializable 接口,所以只要数组中元素的类型能被序列化,数组就能被序列化。而且,所有数组都有一个名为 length 的字段,这个字段的修饰符是 public final int,表示数组中元素的数量。
数组类型放大转换
因为数组扩展自 Object 类,而且实现了 Cloneable 和 Serializable 接口,所以任何数组类型都能放大转换成这三种类型中的任何一种。而且,特定的数组类型还能放大转换成其他数组类型。如果数组中的元素类型是引用类型 T,而且 T 能指定给类型 S,那么数组类型 T[] 就能指定给数组类型 S[]。注意,基本类型的数组不能放大转换。例如,下述代码展示了合法的数组放大转换:
String[] arrayOfStrings; // 创建字符串数组int[][] arrayOfArraysOfInt; // 创建 int 二维数组// String 可以指定给 Object,// 因此 String[] 可以指定给 Object[]Object[] oa = arrayOfStrings;// String 实现了 Comparable 接口// 因此 String[] 可以视作 Comparable[]Comparable[] ca = arrayOfStrings;// int[] 是 Object 类的对象,因此 int[][] 可以指定给 Object[]Object[] oa2 = arrayOfArraysOfInt;// 所有数组都是可以复制和序列化的对象Object o = arrayOfStrings;Cloneable c = arrayOfArraysOfInt;Serializable s = arrayOfArraysOfInt[0];
因为数组类型可以放大转换成另一种数组类型,所以编译时和运行时数组的类型并不总是一样。
把引用类型的值存储在数组元素中之前,编译器通常必须插入运行时检查,确保运行时这个值的类型和数组元素的类型匹配。如果运行时检查失败,会抛出 ArrayStoreException 异常。
与 C 语言兼容的句法
如前所示,指定数组类型的方法是在元素类型后加上一对中括号。为了兼容 C 和 C++,Java 还支持一种声明变量的句法:中括号放在变量名后面,元素类型后面可以放也可以不放中括号。这种句法可用于局部变量,字段和方法的参数。但不推荐使用。例如:
// 这行代码声明类型为 int,int[] 和 int[][] 的局部变量
int justOne, arrayOfThem[], arrayOfArrays[][];
// 这三行代码声明的字段属于同一种数组类型
public String[][] aas1; // 推荐使用的 Java 句法
public String aas2[][]; // C 语言的句法
public String[] aas3[]; // 令人困惑的混用句法
// 这个方法签名包含两个类型相同的参数
public static double dotProduct(double[] x, double y[]) { ... }
