4.1 数组的定义及使用

数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组(简称为数组)、二维数组、多维数组等概念。

4.1.1 一维数组

数组可以存放上千万个数据,并且这些数据的类型是完全相同的。

要使用Java的数组,必须经过声明数组和分配内存给该数组两个步骤。这两个步骤的语法结构如下:

【格式4-1 一维数组的声明与分配内存】

此外,对于数组的声明方式也有以下一种形式:

这两种语法本身没有任何的区别,在本书中将使用第一种数组声明方式,此种数组因为数组名称之后只有一个“[]”,所以也可以称为一维数组。

数组的声明格式里,“数据类型”是声明数组元素的数据类型,常见的类型有整型、浮点型与字符型等。“数组名”是用来统一这组相同数据类型的元素的名称,其命名规则和变量的相同,建议读者使用有意义的名称为数组命名。数组声明后实际上是在栈内存中保存了此数组的名称,接下来便是要在堆内存中配置数组所需的内存。其中,“长度”是告诉编译器,所声明的数组要存放多少个元素,而new则是命令编译器根据括号里的长度,在堆内存中开辟一块堆内存供该数组使用。下面的程序是关于一维数组的声明并分配内存给该数组:

上面程序的第1行,当声明一个整型数组score时,score可视为数组类型的变量,此时,这个变量并没有包含任何内容,编译器仅会在栈内存中分配一块内存给它,用来保存指向数组实体的地址的名称,如图4-1所示。

图4-1 声明一个整型数组

说明

提问:数组声明的疑问?

在数组的声明上为什么要写上一个“null”呢?如“int score[]=null;”。

回答:null表示引用数据类型的默认值。

数据类型划分上曾经讲解过,数组属于引用数据类型,那么对于引用数据类型来说其默认值就是null,表示暂时还没有任何指向的内存空间。在JDK 1.5之后可以不用给数据默认值,但是为了开发方便,本书建议读者在声明变量时一定要给出默认值。

声明之后,接着要做堆内存分配的操作,也就是上例中第2行语句。这一行会开辟3个可供保存整数的内存空间,并把此内存空间的参考地址赋给score变量。其内存分配的流程如图4-2所示。

图4-2 内存分配流程图

从图4-2可以发现,一个数组开辟了堆内存之后,将在堆内存中保存数据,并将堆内存的操作地址给了数组名称score。如第3章所述,数组是引用数据类型,因此数组变量score所保存的并非是数组的实体,而是数组堆内存的参考地址。

提示

堆栈内存的解释。

数组操作中,在栈内存中保存的永远是数组的名称,只开辟了栈内存空间数组是永远无法使用的,必须有指向的堆内存才可以使用,要想开辟新的堆内存则必须使用new关键字,然后只是将此堆内存的使用权交给了对应的栈内存空间,而且一个堆内存空间可以同时被多个栈内存空间指向,即一个人可以有多个名字,人就相当于堆内存,名字就相当于栈内存。如图4-3所示。

图4-3 人和名字的关系

除了用格式4-1的这两行来声明并分配内存给数组外,也可以用较为简洁的方式,把两行缩成一行来编写,其格式如下:

【格式4-2 声明数组的同时分配内存】

上述的格式会在声明的同时分配一块内存空间供该数组使用。下面的程序是声明整型数组score,并开辟可以保存10个整数的内存空间给score数组。

在Java中,由于整数数据类型所占用的空间为4个字节,而整型数组score可保存的元素有10个,所以上例中占用的内存共有4*10=40个字节。图4-4是数组score的保存方式。

图4-4 数组score的保存方式

4.1.2 数组中元素的表示方法

想要访问数组里的元素,可以利用索引来完成。Java的数组索引编号由0开始,以score[10]整形数组为例,score[0]代表第1个元素,score[1]代表第2个元素…score[9]为数组中第10个元素(也就是最后一个元素)。图4-5为score数组中元素的表示法及排列方式。

图4-5 数组中元素的排列

【例4.1】数组的声明及输出

程序执行结果:

从上面程序中可以发现,对于数组的访问采用“数组名称[下标]”的方式,之前一共开辟了3个空间大小的数组,所以下标的取值是0~2,假设程序中取出的内容超过了这个下标,如score[3],则程序运行的时候会出现以下的错误提示:java.lang.ArrayIndexOutOfBoundsException:3。

提示的内容为数组索引超出绑定异常,这一点是在使用数组中经常出现的问题,读者在编写程序时应该引起注意。此外,可以发现以上数组中的内容都是0,这是因为声明的是整型数组,而此时又没有为整型数组中的内容赋值,所以现在都是默认值,整型的默认值是0,下面的范例为数组中的元素进行赋值并进行输出。

【例4.2】为数组中的元素赋值并进行输出

程序执行结果:

上面程序的作用是将奇数赋值给数组中的每个元素。程序的内存操作流程如图4-6所示的图形。

要特别注意的是,在Java中取得数组的长度(也就是数组元素的长度)可以利用“数组名称.length”的形式,如下面的格式:

【格式4-3 数组长度的取得】

【例4.3】取得一个数组的长度

程序执行结果:

图4-6 内存操作流程

4.1.3 数组的静态初始化

数组的内容分为动态初始化和静态初始化两种,之前所讲解的全部代码是采用先声明数组之后为数组中的每个内容赋值的方式完成的,所以属于数组的动态初始化。也可以通过数组静态初始化,在数组声明时就指定其具体内容。

如果想直接在声明时就给数组赋初值,可以利用大括号完成。只要在数组的声明格式后面再加上初值的赋值即可,如下面的格式:

【格式4-4 数组赋初值】

在大括号内的初值会依序指定给数组的第1、2、…、n+1个元素。此外,在声明时,并不需要将数组元素的个数列出,编译器根据所给出的初值个数来判断数组的长度。如下面的数组静态初始化方式所示:

在上面的语句中声明了一个整型数组score,虽然没有特别指明数组的长度,但是由于大括号里的初值有6个,编译器会分别依序指定给各元素存放,score[0]为91,score[1]为92,…,score[5]为96。

【例4.4】数组的静态初始化

程序执行结果:

4.1.4 数组应用范例

前面已经为读者介绍了一维数组的使用及基本注意事项,下面通过一道范例来介绍数组的一些基本应用。

【例4.5】求出数组中的最大和最小值

程序执行结果:

本程序将变量min与max初值设成数组的第1个元素后,再逐一与数组中的各元素相比。比min小,就将该元素的值指定给min存放,使min的内容保持最小;同样,当该元素比max大时,就将该元素的值指定给max存放,使max的内容保持最大。for循环执行完,也就表示数组中所有的元素都已经比较完毕,此时变量min与max的内容就是最小值与最大值,此过程如图4-7所示。

图4-7 循环的比较过程

在数组的操作中,数组的排序也是一种比较常见的操作,下面的范例为一个数组的排序操作。

【例4.6】对整型数组按照由小到大的顺序进行排列

程序执行结果:

上面的程序采用了冒泡算法进行排序。即把数组中的每一个元素进行比较,如果第i个元素大于第i+1个元素,就把两个数字进行交换,这样进行反复的比较就可以将一个数组按照由小到大的顺序进行排序。

【例4.7】修改之前代码,显示每次的排序结果

程序执行结果:

图4-8 一个二维数组就相当于一个表格

读者可以根据上面的结果,观察每次循环后的数组内容顺序的改变。

4.1.5 二维数组

如果说可以把一维数组当成几何中的线性图形,那么二维数组就相当于一个表格,像Excel中的表格那样,如图4-8所示。

二维数组声明的方式和一维数组类似,内存的分配也一样是用new这个关键字。其声明与分配内存的格式如下:

【格式4-5 二维数组的声明格式】

与一维数组不同,二维数组在分配内存时,必须告诉编译器二维数组行与列的个数。因此在格式4-5中,“行的个数”是告诉编译器所声明的数组有多少行,“列的个数”则是说明该数组有多少列,如下面的程序:

同样地,可以用较为简洁的方式来声明数组,其格式如下:

【格式4-6 二维数组的声明格式】

若用上述的写法,则是在声明的同时,就开辟了一块内存空间,以供该数组使用。编写的程序如下:

上面的语句中,整型数据score可保存的元素有4*3=12个,而在Java中,int数据类型所占用的空间为4个字节,因此,该整型数组占用的内存共为4*12=48个字节,如图4-9所示。

图4-9 二维数组存储

【例4.8】二维数组的定义及使用

程序执行结果:

从上面程序中读者应该可以找到一个规律,即一维数组如果要想全部输出,则需要使用一层循环,而二维数组要想全部输出,则使用了两层循环,同理,对于N维数组,则要使用N层循环。

二维数组也可以利用大括号进行静态初始化。只要在数组的声明格式后面再加上所赋的初值即可,如下面的格式:

【格式4-7 二维数组静态初始化格式】

要特别注意的是,用户不需要定义数组的长度,因此,在数组名后面的中括号里不必填入任何的内容。此外,在大括号内还有几组大括号,每组大括号内的初值会依序指定给数组的第0,1,…,n行元素,如图4-10所示。

图4-10 静态初始化二维数组

下面是关于二维数组score声明及赋初值的范例。

【例4.9】使用静态初始化声明一个二维数组

程序执行结果:

4.1.6 多维数组

经过前面一、二维数组的练习后不难发现,想要提高数组的维数,只要在声明数组时将索引与中括号再加一组即可,所以三维数组的声明为int score[][][],而四维数组为int score[][][][]…,依此类推。

使用多维数组时,输入、输出的方式和一、二维相同,但是每多一维,嵌套循环的层数就必须多一层,所以维数越高的数组其复杂度也就越高。以三维数组为例,下面的程序在声明数组时即赋初值,再将其元素值输出并计算总和。

【例4.10】定义和使用三维数组

程序执行结果:

可见,因为定义的是三维数组,所以在输出的时候使用了3层循环。如果是四维数组输出的话就肯定需要使用4层循环,N维数组就要使用N层循环了,但是一般不建议使用多维的数组进行操作。