Java SE 7 Third Edition 规范翻译之-数组

在 Java 编程语言中,数组是动态创建的对象(§4.3.1),并且可以分配给 Object 类型的变量。类对象的所有方法可以基于数组调用。

一个数组对象包含特定数值个变量。这个数值可以是0,但如果这也就意味着数组为空。数组中的变量没有变量名;相反他们通过使用非负整数索引值来访问数组引用的表达式。假设一个数组拥有n个元素,那么我们说这个数组的长度就是n;数组的这些元素使用整数下标引用从0到n-1。

数组内所有的元素拥有统一的类型,称为数组的元素类型。假设数组的元素类型是T,那么这个类型的数组定义成T[]。

对于浮点数类型的数组元素始终是浮点数类型值集(§4.2.3);同样的,对于双精度类型的数组元素始终是双精度类型值集。不允许浮点数类型的数组的元素不是浮点数类型的值集,同样不允许双精度类型的数组的元素不是双精度类型的值集。

一个数组元素类型的本身可以是数组类型。这种数组元素可以包含子数组的引用。假设开始于任何数组类型,考虑到其元素类型,然后子数组的元素类型,以至类推,最终必须有子数组的元素类型不是数组类型;这个就是原始数组的元素类型,并且这一层的数组结构的元素被称为原始数组。

在某些情况下,一个数组的元素可以是一个数组:当这个元素的类型是对象或者Cloneable或者java.io.Serializable,那么部分或者所有的元素均可以是数组,因为任何数组对象都可以指定这些类型的变量。

10.1 Array Types

数组类型用于声明和映射表达式中(§15.16)。

数组类型写法遵循一个元素类型后面跟一些空的方括号[]。方括号的数量表示数组的维度。

数组的长度不是类型的一部分。

数组元素的类型可以是任意类型,是原始数据类型或者引用类型。特别是:

  • 数组拥有一个接口类型作为允许的元素类型。该数组元素可以是它的值的空引用或者任意实现该接口的类型实例。
  • 数组允许一个抽象类做为其元素类型。该数组的元素可以是它的值的空引用或者是抽象类实现子类的实例。

数组类型的父类型在§4.10.3规定。 数组类型的父类是Object。 每一个数组都实现了Cloneable和java.io.Serializable接口。

10.2 Array Variables

数组类型的变量是一个对象的引用。声明数组类型的变量但不创建数组对象或者说不给数组元素分配任何内存空间,那么它只创建变量本身,变量包含一个数组引用。然而声明初始化部分(§8.3, §9.3, §14.4.1)可以创建一个数组,数组的引用将成为该变量的初始值。

Example 10.2-1. Declarations of Array Variables

int[] ai; // array of int
short[][] as; // array of array of short
short s, // scalar short
aas[][]; // array of array of short
Object[] ao, // array of Object
otherAo; // array of Object
Collection<?>[] ca; // array of Collection of unknown type

上面的定义没有创建数组对象。下面举例说明几个创建数组对象的数组变量声明方法:

Exception ae[] = new Exception[3];
Object aao[][] = new Exception[2][3];
int[] factorial = { 1, 1, 2, 6, 24, 120, 720, 5040 };
char ac[] = { 'n', 'o', 't', ' ', 'a', ' ',
'S', 't', 'r', 'i', 'n', 'g' };
String[] aas = { "array", "of", "String", };

[]可以出现在声明类型开始部分,或者也可以出现在声明的变量部分,例如:

byte[] rowvector, colvector, matrix[];

等价于:

byte rowvector[], colvector[], matrix[][];

在变量声明(§8.3, §8.4.1, §9.3, §14.14, §14.20)时除了一个可变的参数,在声明开头就要指定变量的数组类型,后面跟着变量标识符以及中括号。

例如,局部变量声明:

int a, b[], c[][];

等价于一系列的声明:

int a;
int[] b;
int[][] c;

声明时使用中括号也是C和C++的传统,一般为变量声明的规则。但是Java允许中括号出现在类型或者声明部分,所以局部变量可以这样定义:

float[][] f[][], g[][][], h[]; // Yechh!
is equivalent to the series of declarations:
float[][][][] f;
float[][][][][] g;
float[][][] h;

我们不推荐在数组变量定义部分使用“混合标记”,即中括号既出现在类型部分又出现在声明部分。

一旦数组对象被创建,它的长度不会发生变化。为了让数组变量指向一个不同长度的数组,必须要将这个不同长度数组的引用赋给这个变量。

数组类型的单个变量包含对不同长度数组的引用,因为这个数组的长度不属于其类型的一部分。

假设一个数组变量v拥有A[]类型,其中A是引用类型,然后v可以持有任意数组类型B[]的一个实例的引用,假设B可以被分配给A(§5.2)。这可能会导致以后的任务运行时异常;详见§10.5。

10.3 Array Creation

创建一个数组通过数组创建表达式(§15.10)或者数组初始化。

数组创建表达式中指定元素类型,嵌套数组的维度,至少一个嵌套数组的长度。数组的长度可作为最终实例变量的长度。

数组初始化创建一个数组,并提供所有元素的初始值。

10.4 Array Access

数组元素的访问通过数组访问表达式(§15.13),表达式由数组的引用后跟[]括起来的索引值,如A[i]。

所有的数组都是从0开始,长度为n的数组可以通过整数0至n-1来索引。

Example 10.4-1. Array Access

class Gauss {
	public static void main(String[] args) {
		int[] ia = new int[101];
		for (int i = 0; i < ia.length; i++) ia[i] = i;
		int sum = 0;
		for (int e : ia) sum += e;
		System.out.println(sum);
	}
}

这个程序的输出:

5050

这个程序定义了一个int数组类型的变量ia,即int[]。变量ia被初始化为一个新创建数组对象的引用,由数组创建表达式创建(§15.10)。数组创建表达式指定数组应该有101个元素。数组的长度是可以使用的字段长度,如上所示。程序使用0-100填充数组,最终将这些整数求和,并且打印出结果。

数组必须被int类型值索引;short,byte,或者char值也可以用作索引值,因为他们可以转换成int类型值。

企图通过long值索引访问数组将导致编译时错误。

所有数组访问在运行时检查;企图使用小于0或者大于等于实际数组长度的索引将会抛出ArrayIndexOutOfBoundsException异常。

10.5 Array Store Exception

对于一个类型为A[]的数组,其中A即为引用类型,给数组元素赋值将在运行时检查以确保分配给元素的值符合数组元素类型。

如果分配的值与数组元素的类型不兼容(§5.2)将抛出ArrayStoreException异常。

如果数组的元素类型不是具体化类型,Java虚拟机将不能执行前面段落描述的存储检查。这就是为什么数组创建表达式禁止使用非具体化元素类型(§15.10)。可以声明一个数组元素类型为非具体化类型的变量,但数组变量创建表达式的结果必将导致一个非检查告警(§5.1.9)。

Example 10.5-1. ArrayStoreException

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
	public static void main(String[] args) {
		ColoredPoint[] cpa = new ColoredPoint[10];
		Point[] pa = cpa;
		System.out.println(pa[1] == null);
		try {
			pa[0] = new Point();
		} catch (ArrayStoreException e) {
			System.out.println(e);
		}
	}
}

这个程序的输出:

true
java.lang.ArrayStoreException: Point

变量pa为Point[]类型,变量cpa的值是ColoredPoint[]类型的一个引用。一个ColoredPoint对象能转成为一个Point对象;因此cpa的值可以赋给pa。

参考这个数组pa,例如,测试pa[1]是否为空,不会导致一个运行时类型错误。这是因为数组元素的类型ColoredPoint[]是ColoredPoint,每个ColoredPoint都能代替Point,因为Point是ColoredPoint的父类。

另一方面,分配数组pa可能导致运行时错误。在编译时,分配到pa的元素被检查以确保分配的值是一个Point。但由于pa指向一个ColoredPoint数组的引用,只有运行时分配该类型的值时该分配才是有效的,更具体的说,即ColoredPoint。Java虚拟机在运行时会检查这样的情况确保分配是有效的;如果不是将抛出一个ArrayStoreException异常。

10.6 Array Initializers

数组初始化可以在声明时指定(§8.3, §9.3, §14.4),或者作为一个数组创建表达式的一部分(§15.10),创建一个数组并提供一些初始值。

ArrayInitializer:
{ VariableInitializersopt ,opt }
VariableInitializers:
VariableInitializer
VariableInitializers , VariableInitializer

以下重复§8.3,介绍更详细:

VariableInitializer:
Expression
ArrayInitializer

数组初始化写成一个逗号分隔的表达式列表,使用大括号封装。

数组初始化时最后一个表达式后面的逗号将被忽略。

每个变量初始化赋值必须兼容数组的元素类型,否则将会出现一个编译时错误。

如果数组元素类型初始化时没有指定具体的类型将会出现编译时错误(§4.7)。

数组的长度等于数组初始化时大括号里面变量的个数。虚拟机将为这个新数组分配这个长度的空间。如果没有足够的空间分配给这个数组,数组初始化意外终止并抛出OutOfMemoryError错误。否则一个一维特定长度的数组将被创建,并且每个数组的元素都被初始化为其默认值(§4.12.5)。

变量的初始化立即由数组初始化设定的括号按源码从左到右执行。第n个变量初始化指定数组第n-1个元素的值。如果执行变量初始化意外中止,整个数组的初始化也将以同样的原因意外中止。如果所有的变量初始化表达式正常完成,那么数组初始化正常完成,一个有值的新初始化的数组诞生了。

如果一个数组元素类型为数组类型,则变量初始化时指定元素本身就是一个数组初始化的过程;也就是说,数组初始化是可以嵌套的。在这种情况下,执行嵌套数组初始化构造和通过递归算法初始化一个数组对象,并将其分配给元素。

Example 10.6-1. Array Initializers

class Test {
	public static void main(String[] args) {
		int ia[][] = { {1, 2}, null };
		for (int[] ea : ia) {
			for (int e: ea) {
				System.out.println(e);
			}
		}
	}
}

程序输出:

1
2

试图索引到数组ia的第二个空引用元素导致NullPointerException异常。

10.7 Array Members

数组类型的成员有如下几种情况:

  • 公共的final字段length,表明数组元素的数量,length 是0或者正整数。
  • 公共的方法clone,重写了Object类的clone方法,并且抛出没有检查异常。数组T[] clone方法的返回类型还是T[]。一个多维数组的克隆是浅拷贝,那就是说它只创建了一个新数组,子数组共享。
  • 所有的方法继承自Object类;唯一没有继承Object类的方法就是它的clone方法。

在下面的类中数组因此拥有同样的公共字段和方法:

class A<T> implements Cloneable, java.io.Serializable {
	public final int length = X ;
	public T[] clone() {
		try {
			return (T[])super.clone(); // unchecked warning
		} catch (CloneNotSupportedException e) {
			throw new InternalError(e.getMessage());
		}
	}
}

需要注意的是在上面的示例中,如果数组真是这样实现的话将会产生一个非检查警告(§5.1.9)

另一种情况见§9.6.3.4,对象的公共方法和非公共方法的区别,需要特别小心。

Example 10.7-1. Arrays Are Cloneable

class Test1 {
	public static void main(String[] args) {
		int ia1[] = { 1, 2 };
		int ia2[] = ia1.clone();
		System.out.print((ia1 == ia2) + " ");
		ia1[1]++;
		System.out.println(ia2[1]);
	}
}

程序执行结果:

false 2

表明ia1和ia2变量分别引用了两个不同的数组。

实际上当一个多维数组被克隆时子数组是共享的,看这个例子:

class Test2 {
	public static void main(String[] args) throws Throwable {
		int ia[][] = { {1,2}, null };
		int ja[][] = ia.clone();
		System.out.print((ia == ja) + " ");
		System.out.println(ia[0] == ja[0] && ia[1] == ja[1]);
	}
}

程序执行结果:

false true

结果显示ia[0]数组跟ja[0]是同一个数组。

##10.8 Class Objects for Arrays 每个数组都有一个关联的类对象,跟所有其他的数组共享同样的元素类型。

Example 10.8-1. Class Object Of Array

class Test {
	public static void main(String[] args) {
		int[] ia = new int[3];
		System.out.println(ia.getClass());
		System.out.println(ia.getClass().getSuperclass());
	}
}

程序输出结果:

class [I
class java.lang.Object

这里面的字符串”[I”是int类型数组类对象在运行时的类型签名。

Example 10.8-2. Array Class Objects Are Shared

class Test {
	public static void main(String[] args) {
		int[] ia = new int[3];
		int[] ib = new int[6];
		System.out.println(ia.getClass() == ib.getClass());
		System.out.println("ia has length=" + ia.length);
	}
}

这个程序输出结果:

true
ia has length=3

该程序使用继承自Object的getClass方法,和length属性。第一个println打印两个数组类对象比较结果,结果说明所有int类型的数组都是int[]的实例。

10.9 An Array of Characters is Not a String

与C语言不同,在Java编程语言中,char数组即不是字符串也不是一个以’\u0000’ (the NUL character)结尾的字符数组。

一个字符串对象是不可变的,也就是说它的内容不会改变,而char数组元素是可变的。

在String类中toCharArray方法返回一个包含字符串相同字符序列的字符数组。StringBuffer类实现了可变字符数组的有效方法。