02_oo_14_array

第二十七章 数组

1 简介

数组是一种基本的数据结构。它利用了一片连续的内存,存放多个类型相同的数据或者对象。在Java语言中,数组类型是一种内置的类类型。所以,每个数组都是一个对象,由Java语言动态创建。包含在数组中的数据被称为数组元素。我们可以使用它们在数组中的序号访问这些元素。

当一个数组包含0个元素时,我们称之为空数组。当数组包含n个元素时,该数组的长度为n。数组元素在数组中的位置序号从0开始计算。如果一个数组包含n个元素的话,第一个元素的序号为0,最后一个元素的序号为n-1,以此类推。

数组中的元素类型必须相同,即一个数组可以包含多个int类型的数据,或者包含多个float类型的数据。但是,一个数组不能同时包含int类型数据和float类型的数据,因为一个数组内的元素类型必须相同。数组中元素类型被称为数组的Component Type

一个数组可以包含多个类型相同的基本数组类型的数据,也可以包含多个类型相同的对象。既然数组是一种类类型,那么,一个数组也可以包含在另一个数组中。我们称这种数组为多维数组。

2 基本使用方法

以下是一些数组变量声明的例子。元素类型是int的数组类型使用int[]表示,即在元素类型之后加上一对方括号。这种方法同样适用于元素是对象的数组声明。

int[] arrayOfInt;       // 元素类型为int的数组
float[] arrayOfFloat;   // 元素类型为float的数组
String[] arrayOfString; // 元素类型为String的数组
Object[] arrayOfObject; // 元素类型为Object的数组

上述的数组对象声明仅声明了数组变量,并没有创建任何数组对象。所以,这些数组变量需要初始化之后才能使用。

原始数据类型的数组和字符串数组可以使用下面的字面常量初始化。在等号右边的表达式被称为Array Initializer

int[] arrayOfInt = {1, 2, 3};
float[] arrayOfFloat = {1.0f, 2.0f, 3.0f};
String[] arrayOfString = {"the first element", "the second element"};  // 字符串数组
int[][] = { {1, 2}, {3, 4}};  // 这是一个二维数组

类似的,我们还可以动态的创建数组对象,并将其初始化给一个数组变量。数组对象是由new操作符创建出来的。使用这种方法时,开发人员必须明确指出数组元素的类型,数组的维度和数组长度。例如,在下面的程序中,new int[3]表达式创建出了一个长度为3的一维int类型数组。在数组对象被创建之后,我们通过一个for循环,为其中每一个元素设置初始值。在这个过程中,我们使用了两个数组特性。每一个数组对象都有一个公开的(public)、不变的(final)成员变量length。所以,arrayOfInt.length表示的是数组arrayOfInt的长度,即元素个数。另外,我们还使用了arrayOfInt[i]访问数组arrayOfInt的元素。参数i是元素在数组中的位置。

arrayOfInt[i]被称为数组访问表达式(Array Access Expression)。在一对方括号中,我们可以使用int类型的变量来表示被访问元素在数组中的序号。当然,在程序中,我们也可以使用short、byte或者char类型的变量来表示元素的序号。但是,因为数组访问表达式只接收int类型的数据,所以,此时Java语言会做类型提升(Type Promotion),将short、byte或者char类型的变量提升为int类型。但是,如果我们使用long类型的变量的话,编译器会报告错误。

Java程序会在运行时校验元素的序号。如果该序号为负数或者超出了数组的长度,那么程序会抛出ArrayIndexOutOfBoundsException异常。

int[] arrayOfInt = new int[3];
for (int i = 0; i < arrayOfInt.length; i++) {
    arrayOfInt[i] = i;
}

如果觉得上述的程序比较冗长,希望在一行中完成数组对象初始化的话,我们可以使用匿名数组对象。在创建数组对象的同时完成初始化。在下面的程序中,等号右侧实际上创建出了一个匿名的数组对象。它的初始值为{0, 1, 2},并且将其初始化给变量arrayOfInt。

int[] arrayOfInt = new int[] {0, 1, 2};

因为数组对象是一个对象,所以,数组类型也是继承自Object类的。我们可以在数组对象上使用Object类定义的方法。注意,对于多维数组,clone()使用的是浅复制(Shallow Copying)。类似的,我们也可以获得数组类型对象。每一个数组都自动实现了java.lang.Cloneable和java.io.Serializable接口。

int[] arrayOfInt = {1, 2, 3};
System.out.println(arrayOfInt.getClass().getName());
System.out.println(arrayOfInt.toString());

上述程序的输出结果如下。第一行是arrayOfInt的类名,这是由Java编译器自动生成的。第二行是arrayOfInt对象的惟一标识字符串。前半部分是对象的类型名称,后半部分是对象的唯一标识符(唯一id)。每次运行得到的结果可能会稍有不同。

[I
[I@277050dc

3 数组示例

3.1 数组遍历

Java语言提供了方便的方法遍历一个数组对象。首先,我们可以使用元素的序号来遍历数组中的元素。例如,在下面的程序中,我们通过for循环,将变量i从0増长到2,然后在使用i的值来获取数组中每一个元素,并将其值累加计入变量sum中。

int[] arrayOfInt = {1, 2, 3};
int sum = 0;
for (int i = 0; i < arrayOfInt.length; i++) {
    sum += arrayOfInt[i];
}
System.out.println("The sum of the elements in arrayOfInt is " + sum);

for循环语句还提供了for-each结构,更方便的访问数组中的每一个元素。for-each会按照数组元素的顺序从头到尾的访问每一个元素。在下面的程序中,在每一次循环时,变量val表示的是数组中当前访问的元素。

int[] arrayOfInt = {1, 2, 3};
int sum = 0;
for (int val: arrayOfInt) {
    sum += val;
}
System.out.println("The sum of the elements in arrayOfInt is " + sum);

3.2 java.util.Arrays包

java.util.Arrays包提供了许多方便的接口。我们在本小节介绍和列举一些方法,更多的请查看java.util.Arrays的接口文档。

3.2.1 排序

Arrays类提供了两种数组排序的方法。sort()方法采用了一种快速排序算法,而parallelSort()使用的是一种并行的归并排序算法。sort()和parallelSort()都支持对各种基本数据类型和对象类型的排序,将元素按照升序排列。

int[] arrayOfInt = {1, 2, 3};
Arrays.sort(arrayOfInt);

float[] arrayOfFloat = {1.0f, 2.0f, 3.0f};
Arrays.sort(arrayOfFloat);
3.2.2 二分查找

当需要在数组中查找某一个元素是否存在时,可使用二分查找算法(Binary Search Algorithm)。在使用二分查找算法之前,需要先对该数组进行排序。这也是二分查找算法运行的基本原理和前提。

int[] arrayOfInt = {1, 2, 3};
Arrays.sort(arrayOfInt);
int target = Arrays.binarySearch(arrayOfInt, 2);
System.out.println("The integer 2 is found at index " + target); 
3.2.3 数组复制

Arrays类提供了两种数组复制的方法。copyOf()方法从头开始复制;而copyOfRange()方法则可以指定复制的起始点和终止点。当指定的拷贝范围超出源数组时,拷贝的数据会被截断(truncated)或者使用默认值补充(padded with default values)。

int[] arrayOfInt = {1, 2, 3};
int[] aNewArray = Arrays.copyOf(arrayOfInt, arrayOfInt.length);
3.2.4 数组比较

Arrays.equals()方法逐个比较两个数组中的元素。只有当两个数组中元素个数相同,且每个位置上的元素的值相同时(如果元素类型是基本数据类型,则比较元素的值;如果元素是对象类型,则使用equals()方法进行比较),Arrays.equals()方法才会认为两个数组相同。

int[] arrayOfInt1 = {1, 2, 3};
int[] arrayOfInt2 = {1, 2, 3};
boolean isEqual = Arrays.equals(arrayOfInt1, arrayOfInt2);
3.2.5 转换为流对象

流对象(java.util.stream.Stream)是Java语言函数式编程特性中的一个重要的类。Arrays.stream()可以将数组类型转换成一个Stream对象,以便于后续的处理。

int[] arrayOfInt = {1, 2, 3};
IntStream steamOfInt = Arrays.stream(arrayOfInt);

String[] arrayOfString = {"the first element", "the second element"};
Stream<String> streamOfString = Arrays.stream(arrayOfString);
3.2.4 转换为字符串

将数组对象转换成一个可读的字符串对象是一个方便的功能,常常用于日志记录和调试功能。

int[] arrayOfInt = {1, 2, 3};
System.out.println("We would like to log an important array: " + Arrays.toString(arrayOfInt));

3.3 命令行参数

Java程序使用字符串数组来接收命令行参数(Command-Line Arguments)。每一个Java应用程序都是从main()函数开始运行的。main()函数有一个固定的输入参数args。这个参数是一个字符串数组类型,用于表示运行Java程序时的命令行参数。

public class Example {
    public static void main(String[] args) {
        for (int i = 0; i <args.length; i++) {
            System.out.println(String.format("args[%d]: %s", i, args[i]));
        }
    }
}

如果我们使用如下命令运行上面的程序,

> java Example hello world

那么,我们会得到如下的打印输出。

args[0]: hello
args[1]: world

3.4 二维数组

二维数组也是一种常用的数据结构。当我们需要保存或者表达一个矩阵或者表格时,二维数组是一个比较合适的选择。与一维数组相比,二维数组的类型使用了两对方括号。在下面的代码示例中,我们定义了一个2x2的二维数组,用于表示一个2x2的矩阵。矩阵的第一行为{1, 2},第二行为{3, 4}。

有了一个二维数组变量之后,我们可以通过行和列来访问二维数组中的元素。例如,twoDimentionArray[0][0]表示的是第1行第1列的元素。

int[][] twoDimentionArray = {{1, 2}, {3, 4}};
System.out.println("The element at (0, 0) is " + twoDimentionArray[0][0]);

我们可以通过使用一个双重循环来遍历二维数组中的每一个元素。

int[][] twoDimentionArray = {{1, 2}, {3, 4}};
int sum = 0;
for (int[] array: twoDimentionArray) {
    for (int val: array) {
        sum += val;
    }
}
System.out.println("The sum of the elements in twoDimentionArray is " + sum);

4 小结

数组是Java语言内置支持的一种数据结构。一个数组中的数据存放在一片连续的存储空间中。所以,当我们按照元素的序号访问数组元素时,数组能够提供较好的存取性能。与此同时,因为数组的结构非常简单,在数组上实现查找和排序算法也是非常方便和高效的。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.