数组

数组

数组是一种容器对象,用于存储固定数量的单一类型值。数组的长度在创建时确定,创建后长度固定不变。你在”Hello World!”应用程序的主方法中已经见过数组的示例。本节将更详细地讨论数组。

数组中的每个项称为元素,通过其数字索引访问每个元素。如前图所示,编号从0开始。例如,第6个元素的索引为5。
以下程序 ArrayDemo 创建一个整型数组,向数组中赋值,并将每个值打印到标准输出。

public class ArrayDemo {
    public static void main(String[] args) {
        // declares an array of integers
        int[] anArray;

        // allocates memory for 10 integers
        anArray = new int[10];

        // initialize the first element
        anArray[0] = 100;

        //initialize the second element
        anArray[1] = 200;

        // and so forth
        anArray[2] = 300;
        anArray[3] = 400;
        anArray[4] = 500;
        anArray[5] = 600;
        anArray[6] = 700;
        anArray[7] = 800;
        anArray[8] = 900;
        anArray[9] = 1000;
        
        IO.println("Element at index 0: " + anArray[0]);
        IO.println("Element at index 1: " + anArray[1]);
        IO.println("Element at index 2: " + anArray[2]);
        IO.println("Element at index 3: " + anArray[3]);
        IO.println("Element at index 4: " + anArray[4]);
        IO.println("Element at index 5: " + anArray[5]);
        IO.println("Element at index 6: " + anArray[6]);
        IO.println("Element at index 7: " + anArray[7]);
        IO.println("Element at index 8: " + anArray[8]);
        IO.println("Element at index 9: " + anArray[9]);
    }
}

这个程序的输出如下所示:

Element at index 0: 100
Element at index 1: 200
Element at index 2: 300
Element at index 3: 400
Element at index 4: 500
Element at index 5: 600
Element at index 6: 700
Element at index 7: 800
Element at index 8: 900
Element at index 9: 1000

在实际编程场景中,您通常会使用支持的循环结构遍历数组中的每个元素,而非像前例那样逐行编写代码。不过该示例清晰地展示了数组语法。您将在控制流章节中学习各种循环结构(for、while 和 do-while)。

声明变量以引用数组

前面的程序通过以下代码声明了一个名为 anArray 的数组:

// declares an array of integers
int[] anArray;

与其他类型变量的声明类似,数组声明包含两个组成部分:数组类型和数组名称。数组类型写为 type[],其中 type 是包含元素的数据类型;方括号是特殊符号,表示该变量持有数组。数组的大小不属于其类型的一部分(因此方括号为空)。数组名称可自由命名,但须遵循”类”章节所述的命名规则与约定。与其他类型变量相同,声明本身不会实际创建数组,仅告知编译器该变量将存储指定类型的数组。
同样地,你可以声明其他类型的数组:

byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;

你也可以把括号放在数组名称之后:

// this form is discouraged
float anArrayOfFloats[];

然而,惯例不鼓励这种形式;括号用于标识数组类型,应与类型标识符一同出现。

创建、初始化和访问一个数组

创建数组的一种方式是使用new操作符。ArrayDemo程序中的下一条语句分配了一个能容纳10个整数元素的数组,并将该数组赋值给变量anArray。

// create an array of integers
anArray = new int[10];

如果缺少此语句,编译器将输出类似以下的错误信息,编译失败:

ArrayDemo.java:4: Variable anArray may not have been initialized.

接下来几行代码为数组的每个元素赋值:

anArray[0] = 100; // initialize the first element
anArray[1] = 200; // initialize the second element
anArray[2] = 300; // initialize the third element

每个数组元素通过其数字索引(numerical index 为什么中文要叫下标?)进行访问:

IO.println("Element at index 0: " + anArray[0]);
IO.println("Element at index 1: " + anArray[1]);
IO.println("Element at index 2: " + anArray[2]);

或者,你可以使用快捷语法来创建并初始化数组:

int[] anArray = {
    100, 200, 300,
    400, 500, 600,
    700, 800, 900, 1000
}

此处数组的长度由大括号内以逗号分隔的值的数量决定。

创建多维数组

由于数组可以存储任何引用,且数组本身就是一种引用,因此可以轻松创建数组的数组。数组的数组也称为多维数组。可通过使用两个或多个方括号组来声明它们,例如 String[][] names。因此,每个元素都必须通过对应数量的索引值来访问。

在Java编程语言中,多维数组是指其元素本身也是数组的数组。这与C或Fortran中的数组不同——在这些语言中,二维数组是内存中连续的区域,可通过指针直接访问。Java中的数组虽也是内存中连续的区域,但由于二维数组本质上是引用数组,因此其本身并非连续的内存区域。由此产生的结果是,行长度可以变化,如下面的MultiDimArrayDemo程序所示:

public class MultiDimArrayDemo {
    public static void main(String[] args) {
        String[][] names = {
            {"Mr. ", "Mrs. ", "Ms. "},
            {"Smith", "Jones"}
        };

        // Mr.Smith
        IO.println(names[0][0] + names[1][0]);
        // Ms.Jones
        IO.println(names[0][2] + names[1][1]);
    }
}

这个程序的输出如下所示

Mr. Smith
Ms. Jones

这些数组有时候被称为不规则数组,或者锯齿形数组。

##使用数组的长度属性
最后,您可以使用任何数组上定义的内置 length 属性来确定该数组的大小。以下代码将数组的大小打印到标准输出:

IO.println(anArray.length);

这对于嵌套数组尤其有用,其中每个数组的长度可能不同。您的代码不应假设所有这些数组长度相同,而应依赖此长度属性,如下面的代码所示:

void displayBidimensionalArray(String[][] strings){
    for (int arrayIndex = 0; arrayIndex < strings.length; arrayIndex++) {
        for (int index = 0; index < strings[arrayIndex].length; index++) {
            IO.print(strings[arrayIndex][index]+ " ");
        }
        IO.println();
    }
}

你可以使用以下数组调用此方法。

String[][] strings = {
    {"one"},
    {"Maria", "Jennifer", "Particia"},
    {"James", "Michael"},
    {"Washington", "London", "Paris", "Berlin", "Tokyo"}
};

displayBidimensionalArray(strings);

代码的输出如下所示:

one 
Maria Jennifer Particia 
James Michael 
Washington London Paris Berlin Tokyo 

数组的拷贝

System类提供了一个arraycopy()方法,可用于高效地将数据从一个数组复制到另一个数组:

public static void arraycopy(Object src, int srcPos, 
                             Object dest, int destPos, int length)

两个对象参数分别指定要复制的源数组和目标数组。三个整数参数分别指定源数组的起始位置、目标数组的起始位置以及要复制的数组元素数量。
以下程序 ArrayCopyDemo 声明了一个字符串元素数组。它使用 System.arraycopy() 方法将数组中部分元素复制到第二个数组:

public class ArrayCopyDemo {
    public static void main(String[] args) {
        String[] copyFrom = {
            "Affogato", "Americano", "Cappuccino", "Corretto", "Cortado",
            "Doppio", "Espresso", "Frappucino", "Freddo", "Lungo", "Macchiato",
            "Marocchino", "Ristretto"};
        String[] copyTo = new String[7];
        System.arraycopy(copyFrom, 2, copyTo, 0, 7);

        for (String coffee : copyTo) {
            IO.print(coffee + " ");
        }
        IO.println();
    }
}

代码输出如下所示:

Cappuccino Corretto Cortado Doppio Espresso Frappucino Freddo

数组的操作

数组是编程中一种强大且实用的概念。Java SE 提供了执行数组相关常见操作的方法。例如,ArrayCopyDemo 示例使用 System 类的 arraycopy() 方法,而非手动遍历源数组的元素并将每个元素逐个放入目标数组。该操作在后台自动完成,开发者只需一行代码即可调用该方法。
为方便起见,Java SE 在 Arrays 类中提供了多种方法来执行数组操作(如复制、排序和搜索数组等常见任务)。例如,前面的示例可修改为使用 Arrays 类的 Arrays 方法,具体可参见 ArrayCopyOfDemo 示例。其区别在于:使用 Arrays 方法时无需在调用前创建目标数组,因为该方法会直接返回目标数组:

public class ArrayCopyOfDemo {
    public static void main(String[] args) {
         String[] copyFrom = {
            "Affogato", "Americano", "Cappuccino", "Corretto", "Cortado",
            "Doppio", "Espresso", "Frappucino", "Freddo", "Lungo", "Macchiato",
            "Marocchino", "Ristretto"};
        String[] copyTo = Arrays.copyOfRange(copyFrom, 2, 9);

        for (String coffee : copyTo) {
            IO.print(coffee + " ");
        }
        IO.println();
    }
}

如您所见,该程序的输出结果相同,不过所需代码行数更少。请注意,Arrays方法的第二个参数是待复制范围的起始索引(包含该索引),而第三个参数是待复制范围的结束索引(不包含该索引)。在此示例中,待复制的范围不包含索引为9的数组元素(该元素包含字符串 Lungo)。

数组类方法提供的其他有用操作包括:

  • 在数组中搜索特定值以获取其所在的索引位置(the binarySearch() method)。
  • 比较两个数组以确定它们是否相等(equals()方法)。
  • 填充数组以在每个索引处放置特定值(fill()方法)。
  • 将数组按升序排序。这既可通过顺序方式使用 sort() 方法实现,也可通过 Java SE 8 引入的 parallelSort() 方法并行完成。在多处理器系统上,对大型数组进行并行排序的速度快于顺序排序。
  • 创建一个以数组作为源的流(使用 stream() 方法)。例如,以下语句以与前例相同的方式打印 copyTo 数组的内容:
Arrays.stream(copyTo).map(coffee -> coffee + " ").forEach(IO::print);

有关流的更多信息,请参阅聚合操作。

  • 将数组转换为字符串。toString()方法将数组的每个元素转换为字符串,用逗号分隔,然后用方括号包围。例如,以下语句将 copyTo 数组转换为字符串并打印出来:
IO.println(Arrays.toString(copyTo));

该语句的输出如下所示:

[Cappuccino, Corretto, Cortado, Doppio, Espresso, Frappucino, Freddo]

变量与数组的封装

Java编程语言在其术语体系中同时使用”字段”和”变量”的概念。实例变量(非静态字段)是类每个实例专属的。类变量(静态字段)是通过static修饰符声明的字段;无论类被实例化多少次,类变量都仅存在一份副本。局部变量用于存储方法内部的临时状态。参数则是为方法提供额外信息的变量;局部变量和参数均始终被归类为”变量”(而非”字段”)。为字段或变量命名时,存在必须遵循的命名规则与约定。
八种基本数据类型为:byte、short、int、long、float、double、booleancharjava.lang.String 类表示字符串。编译器会为上述类型的字段分配合理的默认值;对于局部变量,则永远不会分配默认值。

字面量是固定值在源代码中的表示形式。数组是一种容器对象,用于存储固定数量的单一类型值。数组的长度在创建时确定,创建后其长度固定不变。