好好学Java:从零基础到项目实战
上QQ阅读APP看书,第一时间看更新

3.3.4 数组工具Arrays

数组作为一种组合形式的数据类型,必然要求提供一些处理数组的简便办法,包括数组比较、数组复制、数组排序等。为此,Java专门设计了Arrays工具,该工具包含几个常用方法,方便程序员加工数组。Arrays工具的常见方法简述如下:

  • Arrays.equals(a1,a2); 判断a1和a2两个数组是否相等,也就是每个元素是否都相等。
  • Arrays.fill(a,val); 往数组a中填入指定的数值val。
  • dest=Arrays.copyOf(src,newLength); 把数组src的内容赋值给数组dest,且dest的长度为newLength。
  • Arrays.sort(a); 对数组a的内部元素排序,默认按照升序排序。

下面详细介绍以上4个数组处理方法的使用。

1.Arrays.equals方法

前面说过,双等号“==”可用来判断两个变量的数值是否相等,但只适合基本变量类型之间的比较,例如比较两个整型变量是否相等、两个双精度数是否相等、两个布尔变量是否相等。若两个数组变量通过“==”判断相等与否,则比较的是这两个数组是否为同一个数组,而不是比较两个数组的所有元素是否都相等。要想判断两个数组内部的每个元素是否一一相等,就必须通过Arrays工具的equals方法来辨别。equals方法返回true表示两个数组的所有元素都相等,返回false表示两个数组至少有一个元素不相等。

2.Arrays.fill方法

在声明数组变量的时候,经常需要对它初始化赋值,比如书店进了10本书,每本书的售价都是99元,那么按照常规写法只能书写10遍99,就像下面的代码这样:

        // 构造一个包含10个99的数组变量
        int[] prices={99, 99, 99, 99, 99, 99, 99, 99, 99, 99};

显然输入重复的数字是个负担,尤其重复数量很多的时候更甚。现在利用Arrays的fill方法,只需一行代码即可对该数组的所有元素都填上相同的数值,于是数组的初始赋值代码便优化为下面这样(完整代码见本章源码的src\com\control\array\ArrayFill.java):

        int[] prices=new int[10];      // 声明一个整型数组,数组大小为10
        Arrays.fill(prices, 99);          // 给整型数组的每个元素全部填写99
        for (int price : prices) {      // 循环遍历并打印整型数组的所有元素数值
            System.out.println("price="+price);
        }

3.Arrays.copyOf方法

把一个数组变量赋值给另一个数组变量,似乎可以用等号直接赋值,这在一般情况下没有问题,但如果赋值之后修改了原数组的某个元素,就会出现问题了。譬如以下的演示代码,先把数组变量pricesOrigin赋值给pricesAssign,接着修改原数组pricesOrigin的元素值,再打印新数组pricesAssign的所有元素(完整代码见本章源码的src\com\control\array\ArrayCopy.java):

        int[] pricesOrigin={99, 99, 99, 99, 99};// 声明一个整型数组,数组大小为5,且5个元素全为99
        // 复制数组的第一个办法:利用等号直接赋值。新数组只是原数组的别名
        int[] pricesAssign=pricesOrigin;
        pricesOrigin[1]=80;
        for (int price : pricesAssign) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("assign price="+price);
        }

运行以上的演示代码,完整的日志输出如下:

assign price=99

assign price=80

assign price=99

assign price=99

assign price=99

没想到打印出来的第二个数组元素竟然变了,可是演示代码明明只改了原数组pricesOrigin,并未修改新数组pricesAssign?让测试程序错乱的缘故是数组之间的等号赋值相当于给数组起一个别名,并非从头到尾完整复制一个新数组出来。既然只是起了别名,那么实际上还是原名称所指的数组,无非是该数组有两个名字罢了。

显然这种情况不是程序员期望的结果,程序员的本意是复制另外的数组,新数组不再与原数组有任何关联,大家井水不犯河水,互不干涉、互不影响,最好克隆一个一模一样的新数组出来。Java恰巧给每个数组变量都提供了clone方法,该方法正是拿来克隆数组用的。克隆出来的新数组分配了单独的存储空间,并且数组元素的数值与原数组完全一致,如此便实现了正常意义上的数组赋值功能。利用clone方法复制数组变量的示例代码如下:

        // 复制数组的第二个办法:调用原数组的clone方法。新数组由原数组克隆而来
        int[] pricesClone=pricesOrigin.clone();
        pricesOrigin[1]=80;
        for (int price : pricesClone) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("clone price="+price);
        }

运行以上示例代码,得到下面的日志输出结果:

clone price=99

clone price=99

clone price=99

clone price=99

clone price=99

可见此时修改了原数组的元素值,并没有改变新数组的元素值,真正做到了完整的复制操作。

clone方法正如其名,它把原数组的所有元素一个不漏地全部复制到新数组,这意味着,如果只想复制部分元素给新数组,clone方法就无能为力了。为此,Java给Arrays工具增配了一个copyOf方法,该方法允许从来源数组复制若干元素给目标数组。当待复制的元素个数恰好等于原数组的大小时,copyOf方法的作用等同于数组变量的clone方法。下面是通过copyOf方法将数组原样复制到新数组的代码例子:

        // 复制数组的第3个办法:调用Arrays工具的copyOf方法。允许复制部分元素
        int[] pricesCopy=Arrays.copyOf(pricesOrigin, pricesOrigin.length);
        for (int price : pricesCopy) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("copy price="+price);
        }

从上面的代码看到,copyOf方法后面跟着两个参数:第一个参数是原数组的名称;第二个参数是要复制的元素个数。接下来,把第二个参数改小一点,看看copyOf方法是否真的支持只复制部分元素。于是第二个参数改为pricesOrigin.length-1之后的代码如下:

        // 改变copyOf方法的第二个参数值,允许复制指定大小的数组元素
        int[] pricesPart=Arrays.copyOf(pricesOrigin, pricesOrigin.length-1);
        for (int price : pricesPart) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("part price="+price);
        }

重新运行修改后的数组复制代码,日志输出结果如下:

part price=99

part price=99

part price=99

part price=99

可以看到新数组的元素只有4个,而原数组共有5个元素,说明此时的确只复制了部分元素。

Arrays工具的copyOf方法还有一个妙用,比如有一个数组分配了初始大小为5,现在想把该数组的长度扩大到10,这时利用copyOf方法就能动态调整数组的大小。具体做法是:调用copyOf方法时,来源数组和目标数组都填该数组的名称,然后待复制的元素大小填写扩大后的长度。下面的代码将演示如何将某数组的大小增大一位:

        // 把copyOf方法的返回值赋给原数组,可以动态调整该数组的大小
        pricesOrigin=Arrays.copyOf(pricesOrigin, pricesOrigin.length+1);
        for (int price : pricesOrigin) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("origin price="+price);
        }

运行调整数组大小的演示代码,观察到以下的日志输出:

origin price=99

origin price=99

origin price=99

origin price=99

origin price=99

origin price=0

由此可见,数组大小果然增大了一位,并且新增的数组元素值为0,这正是整型变量的默认数值。

4.Arrays.sort方法

顾名思义,Arrays工具的sort方法是给数组元素排序的,并且默认的排序结果为升序。sort方法用起来很简单,只要把待排序的数组名称填进圆括号,编译器就会自动完成该数组的排序任务。举一个给整型数组排序的例子,简单的Java实现代码如下:

        int[] prices={ 99, 80, 18, 68, 8 };
        // 对整型数组prices里的元素排序,sort方法得到的结果是升序排列
        Arrays.sort(prices);
        for (int price : prices) {  // 循环遍历并打印整型数组的所有元素数值
            System.out.println("price=" + price);
        }

运行上述的排序代码,得到下面的结果日志:

price=8

price=18

price=68

price=80

price=99

从日志看到,排序后的数组元素从小到大打印,很明显这是升序排列。

当然,在前面的例子中,数组元素早在声明数组时便初始化赋值了,实战性不强。接下来,尝试动态生成一个随机数数组,再对该数组排序,这样更贴近实际业务。详细的实现代码涉及数组、循环、冒号跳转等技术,有兴趣的读者不妨动手实践。下面是生成随机数组并对其排序的代码例子(完整代码见本章源码的src\com\control\array\ArraySort.java):

        int[] numbers=new int[10];  // 创建一个大小为10的整型数组
        loop: for (int i=0; i < numbers.length; i++) {
            int item=new Random().nextInt(100);  // 生成一个小于100的随机整数
            // 下面的循环用来检查数组中是否已经存在该随机数
            for (int j=0; j < i; j++) {
                if (numbers[j] == item) {
                                 // 若已经存在该随机数,则继续第一层循环,重新生成随机数
                    i--;              // 本次循环做了无用功,取消当前的计数
                    continue loop;      // 继续以loop标记的外层循环
                }
            }
            numbers[i]=item;      // 若原数组不存在该随机数,则把随机数加入数组中
        }
        // 对整型数组numbers里的元素排序,sort方法得到的结果是升序排列
        Arrays.sort(numbers);
        for (int number : numbers) {  // 循环遍历并打印整型数组的所有随机数
            System.out.println("number=" + number);
        }