访问数组中的元素

数组中的元素是变量。如果元素出现在表达式中,其计算结果是这个元素中保存的值。如果元素出现在赋值运算符的左边,会把一个新值保存到这个元素中。不过,元素和普通的变量不同,它没有名字,只有编号。数组中的元素使用方括号访问。假如 a 是一个表达式,其计算结果为一个数组引用,那么可以使用 a[i] 索引数组,并引用某个元素。其中,i 是整数字面量或计算结果为 int 类型值的表达式。例如:

  1. // 创建一个由两个字符串组成的数组
  2. String[] responses = new String[2];
  3. // 设定数组的第一个元素
  4. responses[0] = "Yes";
  5. // 设定数组的第二个元素
  6. responses[1] = "No";
  7. // 读取这个数组中的元素
  8. System.out.println(question + " (" + responses[0] + "/" + responses[1] + " ): ");
  9. // 数组引用和数组索引都可以是复杂的表达式
  10. double datum = data.getMatrix()[data.row() * data.numColumns() + data.column()];

数组的索引表达式必须是 int 类型,或能放大转换成 int 的类型:byteshort,甚至是 char。数组的索引显然不能是 booleanfloatdouble 类型。还记得吗,数组的 length 字段是 int 类型,所以数组中的元素数量不能超过 Integer.MAX_VALUE。如果使用 long 类型的表达式索引数组,即便运行时表达式的返回值在 int 类型的取值范围内,也会导致编译出错。

数组的边界

还记得吗,数组 a 的第一个元素是 a[0],第二个元素是 a[1],最后一个元素是 a[a.length-1]

使用数组时常见的错误是索引太小(负数)或太大(大于或等于数组的长度)。在 C 或 C++ 等语言中,如果访问起始索引之前或结尾索引之后的元素,会导致无法预料的行为,而且在不同的调用和不同的平台中有所不同。这种问题不一定会被捕获,如果没捕获,可能过一段时间才会发现。因为在 Java 中容易编写错误的索引代码,所以运行时每次访问数组都会做检查,确保得到能预料的结果。如果数组的索引太小或太大,Java 会立即抛出 ArrayIndexOutOfBoundsException 异常。

迭代数组

为了在数组上执行某种操作,经常要编写循环,迭代数组中的每个元素。这种操作通常使用 for 循环完成。例如,下述代码计算整数数组中的元素之和:

int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
int sumOfPrimes = 0;
for(int i = 0; i < primes.length; i++){
    sumOfPrimes += primes[i];
}

这种 for 循环结构很有特色,会经常见到。Java 还支持 for each 句法。上述求和代码可以改写成下述简洁的代码:

for(int p : primes){
    sumOfPrimes += p;
}

复制数组

所有数组类型都实现了 Cloneable 接口,任何数组都能调用 clone() 方法复制自己。注意,返回值必须校正成适当的数组类型。不过,在数组上调用 clone() 方法不会抛出 CloneNotSupportedException 异常:

int[] data = { 1, 2, 3 };
int[] copy = (int[]) data.clone();

clone() 方法执行的是浅复制。如果数组的元素是引用类型,那么只复制引用,而不复制引用的对象。因为这种复制是浅复制,所以任何数组都能被复制,就算元素类型没有实现 Cloneable 接口也行。

int[] x = {1};
int[][] a = {x, x, x};
int[][] b = a.clone();
int[][] c = new int[a.length][];
System.arraycopy(a, 0, c, 0, a.length);
System.out.println(Arrays.deepToString(a)); // [[1], [1], [1]]
System.out.println(Arrays.deepToString(b)); // [[1], [1], [1]]
System.out.println(Arrays.deepToString(c)); // [[1], [1], [1]]
x[0] = 0;
System.out.println(Arrays.deepToString(a)); // [[0], [0], [0]]
System.out.println(Arrays.deepToString(b)); // [[0], [0], [0]]
System.out.println(Arrays.deepToString(c)); // [[0], [0], [0]]

不过,有时只想把一个现有数组中的元素复制到另一个现有数组中。System.arraycopy() 方法的目的就是高效完成这种操作。你可以假定 Java 虚拟机实现会在底层硬件中使用高速块复制操作执行这个方法。

arraycopy() 方法的作用简单明了,但使用起来有些难度,因为要记住五个参数:

  • 第一个参数是想从中复制元素的源数组;
  • 第二个参数是源数组中起始元素的索引;
  • 第三个参数是目标数组;
  • 第四个参数是目标索引;
  • 第五个参数是要复制的元素数量。

就算重叠复制同一个数组,arraycopy() 方法也能正确运行。例如,把数组 a 中索引为 0 的元素删除后,想把索引为 1 到 n 的元素向左移,把索引变成 0 到 n-1,可以这么做:

System.arraycopy(a, 1, a, 0, n);

数组的实用方法

java.util.Arrays 类中包含很多处理数组的静态实用方法。这些方法中大多数都高度重载,有针对各种基本类型数组的版本,也有针对对象数组的版本。排序和搜索数组时,sort()、和 binarySearch() 方法特别有用。equals() 方法用于比较两个数组的内容。如果想把数组的内容转换成一个字符串,例如用于调试或记录日志,Arrays.toString() 方法很有用。

Arrays 类中还包含能正确处理多维数组的方法,例如 deepEquals()deepHashCode()deepToString()