第5章 数 组
数组是经常使用的一个工具对象,说它是工具是因为在编写程序时经常使用其良好的数据组织特性(一种数据结构),说它是对象是因为在Java中数组是一种动态创建的对象。它包含很多变量,但要求变量的数据类型必须一致。数组中的数据元素没有名称,而是通过一种特殊的访问方式即数组下标来访问数组中的元素值。数组按维数分为一维数组和多维数组,而多维数组中最常用的是二维数组。
本章主要介绍的内容有:
❑一维数组
❑二维数组
❑多维数组
❑数组的常见操作
5.1 一维数组
一维数组是具有相同数据类型数据的一种线性组合。这里的“数据类型”可以是Java定义的任意一种数据类型,包括对象引用类型即对象的引用。数组中可以存放相同类的多个对象。本节按照定义一维数组、初始化一维数组和一维数组的使用依次讲解一维数组。
5.1.1 定义一维数组
一维数组的定义方式是:
datatype arrayName[];或datatype[] arrayName;
其中datatype为任意数据类型,数组中的数据元素也是datatype类型,arrayName是数组名称,在编写程序时该名称应该具有一定的意义。[]是数组的标识符,此时定义了一个数组,但数组中没有数据元素,也就是说Java允许定义一个数组但数据元素的数量为0,通常称这样的数组为空数组。空数组没有获得内存空间,所以无法使用,必须使用new关键字为数据分配内存空间,如下所示。
arrayName=new datatype[size]e;
在Java中new关键字的作用是产生该类的某个对象,并为该对象分配内存空间,内存空间的大小视对象大小而定,如一个double类型的浮点数据对象肯定比int类型的整型数据对象分配的内存空间更大。下面举例定义int型数组。
int IntArrayExample=new int[100];
这里int是声明了数组中元素的数据类型,数组名字是IntArrayExample。new关键字为该数据类型分配内存空间,空间大小是100个整型int型数据的大小,即4字节×100=400字节的内存空间。
5.1.2 初始化一维数组
用new关键字为数组分配了存储空间后,对于数组中的元素“到底是什么”仍然无法确定,所以需要为已经分配了存储空间的数组填充相应的数据,这就是数组初始化的作用。一维数组初始化的格式为:
datatype arrayName=new type{value1,value2,value3};
在数组标识符[]内是type类型的数组值,这里初始化了一个类型为type的一维数组,数组中包含3个type数据类型的数据元素,数据值依次是value1、value2、value3,数据值之间使用“,”隔开。这里的“,”是在英文输入法下输入的,如果是其他输入法输入,在编译时会出现编译错误提示。
当然也可以先定义数组,定义后再初始化数组,如:
datatype arrayName[]; arrayName=new datatype[value1,value2,value3];
举例:
int IntArray[]; IntArray[]=new int[23,25,43,88,99];
5.1.3 使用一维数组
在定义和初始化数组后,就可以使用数组了,这里的“使用”主要强调对数组中数据元素的操作。在本章开始处提到数组元素的获得是通过一种特殊的方式,即通过下标访问数组元素,如果数组的长度为n则可通过从0到n-1的整数索引获得相应位置的元素。数组元素的第一个值的整数索引为0,即IntArray[0]表示数组的第一个数据元素。如数组IntArray:
int IntArray[]={19,78,4,18,77};
数组包含5个数据元素,其中IntArray[0]=19,IntArray[4]=77。
【范例5-1】代码5.1创建一维数组并使用循环语句实现数组元素的访问。该程序首先创建一个int型数组,而后使用for循环依次输出数组的内容。
代码5.1 利用for循环访问一维数组元素示例
1 //定义一个类 2 public class OneArrayOperation{ 3 int IntArray[]={19,78,4,18,77}; //定义int型数组IntArray[] 4 private void printArray(){ //定义一个方法printArray() 5 for(int i=0;i<5;i++){ 6 System.out.println("IntArray["+i+"] is :"+IntArray[i]); 7 } 8 } 9 public static void main(String[] args){ 10 //新建一个类OneArrayOperation的对象onearray; 11 OneArrayOperation onearray=new OneArrayOperation(); 12 //类的对象onearray调用其方法printArray(); 13 onearray.printArray(); 14 } 15 }
【运行效果】程序的执行结果如图5.1所示。
【代码分析】第3行定义一个数组,第5~7行通过for循环依次输出数组的值。
图5.1 访问一维数组元素示例程序执行结果
5.2 二维数组
二维数组是多维数组的一种,这里单独介绍二维数组是因为它是编写程序时使用频率最高的一类多维数组。下面从3个方面介绍二维数组,分别是:定义二维数组、初始化二维数组和如何使用二维数组。相信读者通过本节的学习可以轻松应用二维数组这个有用的工具编写程序。
5.2.1 定义二维数组
二维数组的定义方式是:
datatype arrayName[][];或datatype[][] arrayName;
这里定义了一个二维数组,其中的数据类型为datatype,二维数组名为arrayName,[][]是二维数组的标识符。同样,这里的数组没有实际用处,因为并没有为其分配内存空间,此时,需要使用new关键字实现二维数组的内存分配。其实现方式是:
arrayName[][]=new datatype[rowsize][columnsize];
datatype是二维数组中所存放数据的数据类型,rowsize是二维数组的行的数量,columnsize是二维数组的列的数量。通常二维数组的行的长度称为二维数组的长度。这样为二维数组分配了rowsize*columnsize个内存空间,可以存放datatype类型的数据。例如:
int IntArray[][]=new int[2][3];
定义并初始化了一个整型的二维数组IntArray[][],该二维数组有2行3列,为其分配了2×3=6个内存空间,存放int类型的数据。数组中各元素通过其下标来区分,每个下标的最小值为0,最大值为行数和列数减1。数组包括6个元素,分别是IntArray[0][0]、IntArray[0][1]、IntArray[0][2]、IntArray[1][0]、IntArray[1][1]和IntArray[1][2],相当于2行3列的规则矩阵。
上面定义了规则的二维数组,其实Java并没有对规则性做强制的要求,即允许不规则的二维数组,如:
d[][]= new double[2][]
表示数组d有2个元素,每个元素是数据类型为double的一维数组,即定义了2个数组变量,分别为d[0]、d[1],这时可以用new运算符创建各自的数组对象,如:
d[0]=new double[5] d[1]=new double[3]
数组d的第一行有5个double型数据元素,第二行有3个double型数据元素,所以二维数据每行的长度可以不同。
在定义了二维数组后,经常需要知道数组的长度即数组的行数,或数组的列数。如要取得该数组的长度,只需要在数组名后加上“length”属性即可;如要获得数组中某行元素的数量,则在数组名后加上该行的下标再加上“length”,如:
d.length //获得数组d的行数 d[0].length //获得数组d第1行的数据元素的个数
5.2.2 初始化二维数组
二维数组的初始化是指在定义并为其分配了合适的内存空间后,为每个存储空间填充数据,使得数组有可以操作的实际数据对象。二维数组的初始化有两种方式:一种是在定义时初始化;另一种是在定义完后为每一个位置赋予数据元素。
1)在定义时初始化二维数组。该方式也称为静态初始化,其初始化格式如下:
Int IntArray[][]={{1,2,3},{6,5,4}};
该数组定义并初始化了一个2行3列的二维数组,其中第一行数据是1、2、3,第二行数据是6、5、4。按排列顺序“{}”表示第几行的数据,“{}”内的数据按先后顺序分别是该行相应列的数据,如{6,5,4}是第2行的数据,其中6、5、4是第2行中第1、2、3列的数据。读者可能会发现其实二维数组其实是一维数组的特例,二维数组是含有两个元素的一维数组,每个元素是一个一维数组。
注意这种定义方式不允许在数组下标中出现行、列的数字,如:
Int IntArray[2][3]={{1,2,3},{6,5,4}};
这样的定义是不允许的,如果读者误写,会造成编译时错误。
2)直接赋予初值方式初始化二维数组。以下定义了一个数组IntArray[][]:
1 Int IntArray[][]=new int[10][10]; //定义数组并为数组分配内存空间 2 for(int i=0;i<10;i++){ //通过两个循环为数组赋值 3 for(int j=0;j<10;j++){ 4 IntArray[i][j]=i*j; 5 } 6 }
5.2.3 使用二维数组
二维数组通过两层嵌套来获得数组中的数据,再进行其他运算,当然也可以获得数组中的单独数据。【范例5-2】代码5.2说明二维数组的各种创建方式,包括静态初始化一个常规数据类型、静态初始化对象数组和逐渐构建二维数组。通过3种常用的数组创建方式,读者可以体会和理解不同数据类型的静态和动态创建方式。
代码5.2 创建二维数组事例
1 public class TwoDimArray{ 2 static Random rand=new Random(); 3 static int pRand(int mod){ //定义一个静态方法,产生随机数 4 return Math.abs(rand.nextInt())%mod + 1; 5 } 6 public static void main(String[] args){ 7 int[][] a={{1,2,3},{4,5,6}}; //静态初始化int型数组 8 for(int i=0 ;i<a.length; i++){ //第8~11行打印int型数组的数据元素 9 for(int j=0 ;j<a.length; j++){ 10 System.out.println("a["+i+"]"+"["+j+"]:"+a[i][j]); 11 } 12 } 13 Integer[][] a1={{new Integer(1),new Integer(2)},//第13~16行创建二为维对象数组 14 { new Integer(3),new Integer(4)}, 15 { new Integer(5),new Integer(6)} 16 }; 17 //第17~22行,打印对象数组a1的数据元素 18 for(int i=0 ;i<a.length; i++){ 19 for(int j= 0;j<a[i].length;j++){ 20 System.out.println("a1["+i+"]"+"["+j+"] : "+a1[i][j]); 21 } 22} 23 Integer[][] a2; //声明一个对象数组 24 a2=new Integer[3][]; //逐渐构建对象数组 25 f or(int i=0 ;i<a2.length; i++){ 26 a2[i]=new Integer[3]; 27 for(int j=0 ;j<a2[i].length; j++){ 28 a2[i][j]=new Integer(i*j); 29 } 30} 31 for(int i=0 ;i<a2.length; i++){ 32 for(int j=0 ;j<a.length; j++){ 33 System.out.println("a2["+i+"]"+"["+j+"] : "+a2[i][j]); 34 } } 35 } 36 } 37}
说明
因为本例使用了Random类,所以必须引入“importjava.util.Random;”。本例还不算完善,运行时可能会出现溢出错误,不过不影响程序结果,这里只向读者演示一种二维数组的使用方法。
【运行效果】
a[0][0]:1 a[0][1]:2 a[1][0]:4 a[1][1]:5 a1[0][0]:1 a1[0][1]:2
【代码说明】该程序使用静态方式和动态方式创建数组,数组的数据类型既包括常规数据类型也包括对象类型,对象类型使用的Integer外覆类把int类型数据包装成一个对象。当然,如果读者创建了自己的类,也可以把类本身存储在数组中。对于第24行逐渐构建对象数组说明数组的维数是可以变化的,可以动态创建不等长维数的数组。数组的数据类型既可以是基本类型也可以是对象类型。
5.3 多维数组
多维数组是指三维以上的数组。上节读者详细了解了二维数组,不难看出如果想提高数组的维数,只需要在声明数组时增加下标,再增加中括号即可,如定义四维数组可以在定义二维数组上扩展为double d[][][][],更多维数组的声明方式依此类推。多维数组的使用与一维、二维数组相类似,但是每增加一维,则增加一层嵌套,所以对于多维数组,使用起来相对复杂。
5.3.1 定义多维数组
在定义多维数组前必须先声明数组,这样就明确了数据类型和数组名。以三维数组为例,其格式如下:
数据类型 多维数组名[][][]; 数据类型[][][] 多维数组名;
[][][]是三维数组的标志,其位置如多维数组声明格式所示。例如:
1 String multiStringArray[][][]; //数组标志在数组名后 2 String[][][] multiStringArray; //数组标志在数据类型后 3 byte multiByteArray[][][]; //数组标志在数组名后 4 byte[][][] multiByteArray; //数组标志在数据类型后
声明了多维数组后,只是存在一个数据的名字,但是还没有分配内存空间。所以接下来就需要为定义的数组分配内存空间,像一维数组和二维数组的定义一样使用new运算符,开辟内存空间。例如:
int multiIntArray[][][]=new int[2][3][4];
显然,开辟了2×3×4=24个int型数据的内存空间,用来存放int型数据。数组在使用时除获得数组属性信息(多维数组某行的长度)外,一般使用它的数据元素实现输入、输出、计算功能。这时需要使用下标来区分多维数组中不同位置的元素。例如:
multiIntArray[0].length //数组第一行的长度 multiIntArray[0][1].length //数组第一行第二列的长度 multiIntArray[0][2][3] //数组中一个位置的元素值
说明
多维数组的下标是不能越界的。如果多维数组的第一行有3个数据元素,而想获得multiInt Array[0][3][2]的数据元素,显然超过了第一行的长度3。此时在编译时会触发java.lang.ArrayIndexOut OfBoundsException异常。
5.3.2 初始化多维数组
无论是一维数组还是多维数组,数组初始化的本质是一样的,就是为分配了内存空间的数组填充具体的数据元素。这样的数组才有用。
数据元素分两种,一种是对象类型,另一种是基本数据类型。如果是对象类型首先需要对象的初始化,否则该数组中的对象数据就无法使用;如果是基本类型,Java初始化为默认值。多维数组的初始化也有以下两种。(1)静态初始化
多维数组的静态初始化是在定义数组时进行数据的初始化。
Int ThreeDemisionArray ={{{1,2},{3,4}},{{5,6},{7,8}}};
(2)赋值初始化
在定义了多维数组后(声明并定义了内存空间),系统每个内存空间赋予数据元素的值。例如:
1 //声明并定义一个数组mulitIntArray 2 int multiIntArray[][][]=new int[2][3][4]; 3 //下面代码通过三重循环为数据赋值 4 for(int i=0;i<2;i++){ 5 for(int j=0;j<3;j++){ 6 for(int k=0;k<4;k++){ 7 multiIntArray[i][j][k]=i*j*k; 8 } 9 } 10 }
5.3.3 使用多维数组
数组的使用就是通过下标来获取数据元素的值或通过数组元素的下标修改相应数据的值,多维数组的使用也是如此。
【范例5-3】代码5.3实现访问多维数组的数据元素,该程序依次获得多维数组的数据元素并且按照维数依次输出。显然,一旦获得数组中数据元素后可以实现数据的各种操作,而不仅是打印输出结果。
代码5.3 访问多维数组元素示例
1 class MultiArrayTest{ 2 public static void main(String[] args){ 3 int multiArray[][][]=new int[3][4][5]; //声明和初始化数组multiArray[][][] 4 for(int i=0 ;i<3;i++){ //第6~9行通过三重循环为数组赋值 5 for(int j=0 ;j<4;j++){ 6 for(int k=0;k<5;k++){ 7 multiArray[i][j][k]=i*j*k; 8 System.out.print(multiArray[i][j][k]+" "); 9 } 10 System.out.println(); //实现分行显示 11 } 12 System.out.println(); //实现分行显示 13 } 14 } 15}
【运行效果】代码5.3的执行结果如图5.2所示。
图5.2 访问多维数组元素示例执行结果
【代码说明】在为三维数组赋初始值时,使用3个for循环中的变量的乘积作为三维数组中元素的值。完成整个三维数组的数据元素初始化。正如在二维数组的初始化中使用二层循环一样,在三维数组中使用三层循环来初始化三维数组,这种方式书写工整,也好理解。
5.4 数组操作
本节介绍的数组操作在实际中经常用到,数组操作表现为具体的方法,这些方法供开发人员调用。这些实现数组操作的方法都是静态(static)方法,可以直接调用。如果这些方法的参数为具体的数组引用而该引用为空,数组都将抛出NullPointerException异常。本节重点介绍数组的复制、数组的填充、数组的比较、数组排序和在数组中搜索。
5.4.1 数组的复制
数组的复制是通过类Arrays的静态方法copyOf(type[] original,int length)实现的,其中type可以是boolean类型、int类型、short类型、char类型、byte类型等。
代码5.4说明如何实现数组的复制,以参数为char型的数组为例。
代码5.4 char类型数组的复制示例
1 char charArrayCopy[]={'h','e','l','l','o'}; 2 //对数组charArrayCopy进行复制,新数组的长度为4,截去charArrayCopy中的多余元素 3 Arrays.copyOf(charArrayCopy,4); 4 //对数组charArrayCopy进行复制,新数组长度为8,以null字符填充,其中 5 //charArrayCopy的数组长度为4,新数组的其余部分用null字符填充 6 Arrays.copyOf(charArrayCopy,8);
表5.1详细描述了不同参数类型的copyOf()方法。
表5.1 数组的复制方法列表
5.4.2 数组的填充
数组的填充实现了数组部分或全部空间的填充。Java提供了一种方法两种形式:一种形式是“fill(type[] a, type b);”;另一种形式是“fill(type[] a,int key1,int key2,type b);”。前者表示把数组a的全部空间填充为b,后者表示把数组a从key1到key2的全部内容填充为b,但不包含key2的位置。下面代码详细说明这两种形式的具体用法:
1 char charArrayOne[]={'a','b','c','d','e','f'}; 2 char charArrayTwo[]=new char[5]; 3 //把char类型的数组charArrayOne的第四和第五个位置的元素填充为't' 4 Arrays.fill(charArrayOne,4,6,'t'); 5 //把char类型的数组charArrayTwo的所有元素填充为'p' 6 Arrays.fill(charArrayTwo,'p');
表5.2详细描述了不同参数类型的fill()方法。
表5.2 数组的fill()方法列表
5.4.3 数组的比较
数组的比较是对两个数据类型相同的数组而言的,实现比较的函数为equals(数组1,数组2)。如果两个数组的数据元素数量相同,相同位置上的数据元素又相等,则这两个数组相等,函数返回boolean值true,否则不相等,函数返回boolean值false。
1 char charArray1[]={'a','b','c','e','f','g','h','I','j','k'} 2 char charArray2[]={'a','b','c','e','f','g','h','I','j','k'} 3 double doubleArray1[] ={12.0,99.43,33,2,22,56.9} 4 double doubleArray2[]={12.0,99.43,33} 5 Arrays.equals(charArray1,charArray2); //返回true,类型和数量相同 6 Arrays.equals(charArray1,doubleArray1); //返回false,元素类型不同 7 Arrays.equals(doubleArray1,doubleArray2); //返回false,元素数量不同
Java提供了equals( )方法,这些方法满足不同数据类型的数据比较。表5.3说明了数组的各种比较方法。
表5.3 数组的比较方法列表
5.4.4 数组的排序
数组的排序指依据数组中的数据类型升序排序,如果是整数类型则按照从小到大的顺序,如果是字符(char)类型则按照字母升序排列。数组的排序方法为静态方法,可以直接调用。该方法有两种参数格式:一是“sort(数组引用);”二是“sort(数组引用,参数1,参数2);”。前者对整个数组升序排序,而后者对数组中的一个范围内的元素排序,参数1是起始位置,参数2是截止位置,但不包含该位置。数组排序方法如表5.4所示。
表5.4 数组排序方法列表
【范例5-4】代码5.5实现了char类型数组的排序并打印排序结果。
代码5.5 char类型数组的排序示例
1 //定义一个类SortTest 2 public class SortTest { 3 char charone[]={'d','c','a','b'}; 4 //声明并定义个字符数组部分排序的方法,先部分排序再打印结果 5 private void charArrayPartSort(){ 6 //调用Arrays类的静态方法sort(),第一个参数是数组引用,第二个参数0是起始位置,第二个参数是截止位 7 //置但不包含该位置,即只对前3个元素排序。 8 Arrays.sort(charone,0,3) ; 9 for(int i=0;i<charone.length ;i++){ 10 System.out.println(charone[i]) ; 11 } 12 } 13 //声明并定义一个char类型数组的全排序方法,先全排序再打印排序结果 14 private void charArrayAllSort(){ 15 Arrays.sort(charone) ; 16 for(int i=0;i<charone.length ;i++){ 17 System.out.println(charone[i]) ; 18 } 19 } 20 public static void main(String args[]){ 21 SortTest test=new SortTest(); 22 test.charArrayPartSort() ; //调用该对象的charArrayPartSort()方法 23 test.charArrayAllSort() ; //调用该对象的charArrayAllSort()方法 24 } 25 }
【运行效果】代码5.5的执行结果如图5.3所示。
图5.3 char类型数组排序执行结果
【代码说明】部分排序只对数组的前3个元素排序,即charone[0]、charone[1]、charone[2]。原序是d、c、a,排序后的结果是a、c、d。
5.4.5 数组的查找
数组的查找是在指定数据类型的数组中查找一个具体的元素,如在int型数组中查找一个整数,如果该整数存在,则输出该整数在数组中的位置。需要注意数组中元素的位置是从0开始记数的,如果该整数不存在则输出一个负数。在Java的数组(Arrays)操作中使用二分查找算法实现数组中元素的查找。
【范例5-5】代码5.6实现在int型数组中查找一个具体的元素。如果该元素存在,则打印该元素的位置;如果不存在,则打印“指定数据元素不存在”。
代码5.6 在int型数组中查找元素示例
1 //找出需要的类Arrays,以调用其搜索方法binarySearch(参数1,参数2); 2 Import java.util.Arrays; 3 public class SearchTest { 4 int intone[]={1,2,3,8,9,12,32,44,67,89}; 5 //定义一个数组搜索方法,参数是待搜索的数据 6 private void arraySearchTest(int testint){ 7 //调用Arrays类的静态方法binarySearch(),返回一个int类型数据 8 int result=Arrays.binarySearch(intone,testint) ; 9 if(result > 0){ 10 System.out.println("result is : " + result); 11 } 12 else 13 System.out.println("指定的数据元素不存在" ); 14 } 15 public static void main(String args[]){ 16 SearchTest test=new SearchTest(); //new一个对象test 17 test.arraySearchTest(12); //调用对象的arraySearchTest()方法,参数为12 18 test.arraySearchTest(100); //调用对象的arraySearchTest()方法,参数为100 19 } 20 }
【运行效果】代码5.6的执行结果如图5.4所示。
图5.4 在int型数组中查找元素的执行结果
【代码说明】第4行定义数组,第6~14行定义了数组搜索方法arraySearchTest,第20~21行调用此方法。
表5.5是常用的binarySearch()方法,满足不同数据类型的数组搜索的需要。
表5.5 binarySearch()方法列表
本节重点介绍了数组的几个常用操作,包括:数组复制、数组填充、数组比较、数组排序、数组查找。这些方法可以应用于不同的数据类型,使用方式类似。一些细节问题如方法抛出的异常类型、不同类型数组的不同排序方法等,具体可以参考JavaAPI文档。
5.5 常见面试题分析
5.5.1 如何理解数组在Java中作为一个类
Java语言中的数组本质上是一个类,该类还保存了数据类型的信息。该类通过成员变量的形式来保存数据,并且通过“[]”符号,使用下标来访问这些数据。在处理基本类型数据时(如int型数组),数组保存的是变量的值,如果程序员未提供初始值,数组会把这些变量的值初始化为0;而处理引用类型时(如String型数组),数组保存的是数据的引用,如果程序员未提供初始值,数组会把这些变量的值初始化为null。
5.5.2 new Object[5]语句是否创建了5个对象
答案为否。题目的语句其实是创建了一个数组实例,长度为5,每个数组元素的值均是null,并没有创建5个Object对象。如果需要创建5个Object对象,则需要为每个数组元素分别指定。
5.5.3 二维数组的长度是否固定
长度不固定。Java多维数组的长度是完全根据程序员的要求动态确定的,程序员可以扩展任意长度的维度,每一维度的元素个数都可以是不同的。
5.6 本章习题
1.什么是数组?数组的特点是什么?创建数组包括哪些基本步骤?
2.编写一个声明10行10列的数组语句。
3.编写一个程序,读取键盘输入的26个英文字符,并按照逆序输出。
4.计算10~20的平方,并将结果保存在一个数组中。
注意:
1)使用数组一定要先声明再初始化。对于基本类型的数组可以使用默认初始化,此时数组也可以使用(没什么意义);对于对象类型数组则必须初始化数组,使得数组中包含实例对象。
2)使用多维数组时,尽量不要使用三重以上的循环。如果出现最好拆解成多个三重以下的循环,以减少程序的复杂度,提高程序的可读性。