
2.5 数组
2.5.1 一维数组
数组是用来存放多个同类型值的一种结构,数组本身也是对象,如图2.13所示。数组一旦创建完毕,就不能再改变其长度(即不能改变所能存储同类型值的个数)。

图2.13 数组示意图
下面的语句声明了一个整型数组,数组名称为anIntArray,表示数组anIntArray中只能存储整型值:
int[] anIntArray; // 声明一个数组
注意:也可以使用int anIntArray[]来声明一个数组。
可以发现,声明一个数组与声明一个变量类似,不同之处在于声明数组时,在数组变量名称之前多了一个[],[]表示所声明的变量是一个数组。类似地,可以声明任何其他类型的数组,例如float、double以及string类型:
float [] aFloatArrary; double [] aDoubleArrary; String [] aStringArrary;
声明一个变量为数组类型的变量,并没有真正分配空间用以存放数组元素。使用new操作符为一个数组类型的变量分配存储空间。例如,要为上面的数组变量anIntArray分配一个长度为8的整型数组:
anIntArray=new int[8];
数组创建完毕后,就可以对数组元素进行访问:对数组元素赋值或是读取数组元素的值。下面的代码给数组anIntArray赋值并打印出各数组元素的值:
for(int i = 0; i<anIntArray.length; i++){ anIntArray[i] =8-i; //为数组元素赋值 System.out.print(anIntArray[i]+ " "); //打印数组元素 }
访问数组元素总是使用格式:
数组名[位置索引]
其中位置索引可以是一个具体值,也可以是一个表达式。Java中,数组元素的位置索引总是从0开始的,如果一个数组的长度为n,则最后一个数组元素的位置索引是n-1。
如果要获取数组的长度,可以使用:
数组名.length
例如上面的代码片段中使用anIntArray.length来获取数组的长度,以用来判断是否结束循环。
还可以在创建数组的同时进行赋值,例如:
int[] anIntArray={8,7,6,5,4,3,2,1};
使用这种方式创建的数组,其长度是由大括号之间的数组元素个数所确定的。同样,数组一旦创建,其长度不能再改变。
注意:如果希望在一个数组中存储不同类型的数据,并且可以动态改变数组的大小,那么可以使用java.util.Vector。
对于一个可以运行的类,其中总是含有一个main方法:
public static void main(String[] args){}
在第1章中我们已经知道,main方法的参数是一个字符串数组,接收来自命令行的参数。也就是说,在命令行输入的参数是以一个字符串数组的形式传入main方法的。
下面通过一个计算器程序来进一步演示命令行参数以及数组的使用。
例2.7 SimpleCalculator.java
/** 一个简单的计算器,完成两个整数的 * 加、减、乘、除运算 * 参与运算的两个整数及运算符从命令行参数传入 * 例如,要计算100 + 200 * 则在命令行输入: * java SimpleCalculator 100 + 200 */ public class SimpleCalculator{ public static void main(String[] args){ if(args.length!=3){ System.out.println("Usage: java SimpleCalculator "+ "operand1 operator operand2"); System.out.println("Example: java SimpleCalculator 100 + 200"); System.exit(-1); } int oprand1=Integer.parseInt(args[0]); String operator=args[1]; int oprand2=Integer.parseInt(args[2]); int result=Integer.MIN_VALUE; if(operator.equals("/")&&oprand2==0){ System.out.println("can not divide by 0!"); System.exit(-1); } if(operator.equals("+")) result=oprand1+oprand2; else if(operator.equals("-")) result=oprand1-oprand2; else if(operator.equals("*")) result=oprand1*oprand2; else if(operator.equals("/")) result=oprand1/oprand2; else{ System.out.println("Error operator!"); System.exit(-1); } System.out.println(args[0]+args[1]+args[2]+"="+result); } }
若在命令行输入:
java SimpleCalculator 100 + 200
程序运行后将输出:
100+200=300

图2.14 命令行参数的存储
观察程序的输入、输出,可以发现:命令行参数指的是命令行中程序名之后的内容,并且不同的参数是以空格来分开的。
在对象数组中,数组元素中存储的值为对象的地址。上例中,字符串数组args的存储情况如图2.14所示。
由于通过命令行得到的参数都是字符串类型的,要进行四则运算,必须先转换成数值类型。使用Integer类中的类方法parseInt,可以将字符串转化为int类型的数值,例如:
int oprand1=Integer.parseInt(args[0]);
需要说明的是,字符串中的每个字符必须是一个十进制数字(但是首字符可以是一个负号),当字符串中包含非数字字符时,该方法将出现异常,无法进行正确的转换。例2.7中假定用户输入的均为可以转换成整数的字符串。在读者学习完异常处理后,还可以进一步考虑用户输入非法数据的情形。
与将字符串转化为整数类型相似,也可以将字符串转化为double或是float类型:
double Double.parseDouble(String str); float Float.parseFloat(String str);
在例2.7中,还使用了System类中的exit方法。exit方法接收一个整数类型的参数(称为退出代码),该方法一旦执行,程序立刻退出运行,并将退出代码传送给操作系统。一般情况下,退出代码0表示程序正常结束,不同的非零退出代码表示程序在运行过程中出现的不同错误。
2.5.2 数组复制
System类中提供了一个静态方法用于数组复制:
public static void arraycopy(Object src, int srcIndex, Object dest, int destIndex, int length)
该方法共有5个参数:src是源数组,srcIndex是源数组的起始复制位置,dest是目标数组,destIndex是目标数组的起始位置,length是复制数组元素的个数,如图2.15所示。

图2.15 数组复制
图2.15所示的数组复制可以用Java语句表示为:
System.arraycopy(src,2,dest,5,3);
2.5.3 多维数组
通过前面的学习,已经知道:数组中的数组元素可以是基本数据类型的值,也可以是对象类型的值。由于数组也是对象,因此,数组中的每个元素还可以是一个数组,如图2.16所示:m是一个长度为3的数组,其中每一个数组元素又是一个长度为4的数组,这时候,称m是一个二维数组。要生成数组m,可以使用语句:

图2.16 二维数组
int [][]m=new int[3][4];
还可以使用下面的方式:
int [][]m=new int[3][]; //先生成一个长度为3的数组 for(int i=0;i<m.length;i++) m[i]=new int[4]; //再生成每个元素是长度为4的子数组的数组
如果已经知道二维数组中存储的值是什么,还可以在生成数组的同时进行数组的赋值工作:

图2.17 不规则的二维数组
int [][]m={{0,1,2,3}, {4,5,6,7},{8,9,10,11}};
除了生成规则的数组外,还可以生成不规则的数组,如图2.17所示。
对应的代码如下:
int [][]m=new int[3][]; //先生成一个长度为3的数组 m [0]=new int[4]; //再生成长度为4的子数组 m [1]=new int[2]; //再生成长度为2的子数组 m [2]=new int[3]; //再生成长度为3的子数组
同样,如果已经知道该不规则数组中要存储的值,也可以使用如下方式:
int [][]m={{0,1,2,3}, {4,5},{8,9,10}};
使用一个二重循环就可以遍历一个二维数组:
int [][]m={{0,1,2,3},{4,5},{8,9,10}}; for(int i=0;i<m.length;i++){ for(int j=0;j<m[i].length;j++){ System.out.print(m[i][j]+" "); } System.out.println(); }
屏幕上将输出:
0 1 2 3 4 5 8 9 10
如果二维数组m中每个子数组的数组元素也是一个数组,那么就得到了三维数组。类似地,可以方便地构建出更高维数的数组。
我们以一个在二维不规则数组中查找最大值的程序示例来结束本节。
例2.8 FindMax.java
/** 从二维不规则数组中查找最大值 * 并指明最大值所在的行号和列号 */ public class FindMax{ public static void main(String[] args){ int [][]m={{0,1,2,3},{400,5},{8,9,10}}; int max=m[0][0]; int row=0; int column=0; for(int i=0;i<m.length;i++) for(int j=0;j<m[i].length;j++){ if(m[i][j]>max){ max=m[i][j]; row=i; column=j; } } System.out.println("max="+max+" locate at row="+ row+" column="+column); } }
该程序输出结果为:
max=400 locate at row=1 column=0
2.5.4 Java 8增强的数组功能
Java 8增强了Arrays类的功能,为Arrays类增加了一些工具方法,这些工具方法可以充分利用多CPU并行的能力来提高设值、排序的性能。下面是Java 8为Arrays类增加的工具方法。
void parallelPrefix(xxx[] array, XxxBinaryOperator op):该方法使用op参数指定的计算公式计算得到的结果作为新的元素。op计算公式包括left、right两个形参,其中left代表数组中前一个索引处的元素,right代表数组中当前索引处的元素,当计算第一个新数组元素时,left的值默认为1。
void parallelPrefix(xxx[] array, int fromIndex, int toIndex, XxxBinaryOperator op):该方法与上一个方法相似,区别是该方法仅重新计算fromIndex到toIndex索引的元素。
void setAll(xxx[] array, IntToXxxFunction generator):该方法使用指定的生成器(generator)为所有数组元素设值,该生成器控制数组元素的值的生成算法。
void parallelSetAll(xxx[] array, IntToXxxFunction generator):该方法的功能与上一个方法相同,只是该方法增加了并行能力,可以利用多CPU并行来提高性能。
void parallelSort(xxx[] a):该方法的功能与Arrays类以前就有的sort()方法相似,只是该方法增加了并行能力,可以利用多CPU并行来提高性能。
void parallelSort(xxx[] a, int fromIndex, int toIndex):该方法与上一个方法相似,区别是该方法仅对fromIndex到toIndex索引的元素排序。
Spliterator.OfXxx spliteraator(xxx[])array):将该数组的所有元素转换成对应的Spliterator对象。
Spliterator.OfXxx spliteraator(xxx[])array, int startInclusive, int endExclusive):该方法与上一个方法相似,区别是该方法仅转换startInclusive到endExclusive索引的元素。
XxxStream stream(xxx[] array):该方法将数组转换为Stream, Stream是Java 8增加的流式编程API。
XxxStream stream(xxx[] array, int startInclusive, int endExclusive):该方法与上一个方法相似,区别是该方法仅将startInclusive到endExclusive索引的元素转换为Stream。
上面方法列表中,所有以parallel开头的方法都表示该方法可利用CPU并行的能力来提高性能。上面方法中的xxx代表不同的数据类型,比如处理int[]型数组时应将xxx换成int,处理long[]型数组时应将xxx换成long。