2020重新出发,JAVA基础,数组

数组(Array)

什么是数组

数组是最常见的一种数据结构,它是相同类型的用一个标识符封装到一起的基本类型数据序列或者对象序列。数组使用一个统一的数组名和不同的下标来唯一确定数组中的元素。实质上,数组是一个简单的线性序列,因此访问速度很快。

在某些情况下,虽然可以使用单个变量来存储信息,但是如果需要存储的信息较多(例如存储 50 名学生的成绩),这时再依次创建变量声明并赋值显得非常麻烦。

随着处理的信息量越来越大,工作也就越来越烦琐,这时可以使用数组或集合来存储信息。通过使用数组,可以在很大程度上缩短和简化程序代码,从而提高应用程序的效率。

数组(array)是一种最简单的复合数据类型,它是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来确定数组中唯一的元素。根据数组的维度,可以将其分为一维数组、二维数组和多维数组等。

在计算机语言中数组是非常重要的集合类型,大部分计算机语言中数组具有如下三个基本特性:

  1. 一致性:数组只能保存相同数据类型元素,元素的数据类型可以是任何相同的数据类型。
  2. 有序性:数组中的元素是有序的,通过下标访问。
  3. 不可变性:数组一旦初始化,则长度(数组中元素的个数)不可变。

总的来说,数组具有以下特点:

  • 数组可以是一维数组、二维数组或多维数组。
  • 数值数组元素的默认值为 0,而引用元素的默认值为 null。
  • 数组的索引从 0 开始,如果数组有 n 个元素,那么数组的索引是从 0 到(n-1)。
  • 数组元素可以是任何类型,包括数组类型。
  • 数组类型是从抽象基类 Array 派生的引用类型。

在 Java 中数组的下标是从零开始的,很多计算机语言的数组下标也从零开始。Java 数组下标访问运算符是中括号,如 intArray[0],表示访问 intArray 数组的第一个元素,0 是第一个元素的下标。Java 中的数组本身是引用数据类型,它的长度属性是 length。

你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99。

一个数组包括:数组名、下标(索引)、元素、数组的长度

数组也是一种数据类型

Java 的数组要求所有的数组元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。

因为 Java 语言是面向对象的语言,而类与类之间可以支持继承关系(从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为),这样可能产生一个数组里存放多种数据类型的假象。例如有一个水果数组,要求每个数组元素都是水果,实际上数组元素既可以是苹果,也可以是香蕉(苹果、香蕉都继承了水果,都是一种特殊的水果),但这个数组的数组元素的类型还是唯一的,只能是水果类型。

一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。即使把某个数组元素的数据清空,但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。

Java 的数组既可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。

值得指出的是,数组也是一种数据类型,它本身是一种引用类型。例如 int 是一个基本类型,但 int[](这是定义数组的一种方式)就是一种引用类型了。

int[] 是一种类型吗?怎么使用这种类型呢?

没错,int[] 就是一种数据类型,与 int 类型、String 类型相似,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等。使用 int[] 类型来定义变量、进行类型转换时与使用其他普通类型没有任何区别。int[] 类型是一种引用类型,创建 int[] 类型的对象也就是创建数组,需要使用创建数组的语法。

一维数组

当数组中每个元素都只带有一个下标时,这种数组就是“一维数组”。一维数组(one-dimensional array)实质上是一组相同类型数据的线性集合,是数组中最简单的一种数组。

数组是引用数据类型,引用数据类型在使用之前一定要做两件事情:声明和初始化。

创建数组

数组本身是引用数据类型,数组的元素可以是任意数据类型,包括基本数据类型和引用数据类型。

创建数据对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。

需要注意的是:数组的长度一旦确定,就不能修改

Java语言使用new操作符来创建数组,语法:

type[] arrayName = new type[arraySize]; // 数据类型[] 数组名;

上面的语法语句做了两件事:

  • 使用 dataType[arraySize] 创建了一个数组。
  • 把新创建的数组的引用赋值给变量 arrayRefVar。

创建数组还有另一种语法:

type arrayName[];     // 数据类型 数组名[];

可见数组的声明有两种形式:一种是中括号”[]“跟在元素数据类型之后,另一种是中括号”[]“跟在变量名之后。

对于以上两种语法格式而言,Java 更推荐采用第一种声明格式,因为第一种格式不仅具有更好的语意,而且具有更好的可读性。对于第一种格式type[] arrayName,很容易理解这是定义一个变量,其中变量名是 arrayName,而变量类型是 type[]。

前面已经指出:type[] 确实是一种新类型,与 type 类型完全不同(例如 int 类型是基本类型,但 int[] 是引用类型)。因此,这种方式既容易理解,也符合定义变量的语法。但第二种格式type arrayName[]的可读性就差了,看起来好像定义了一个类型为 type 的变量,而变量名是 arrayName[],这与真实的含义相去甚远。

可能有些读者非常喜欢type arrayName[]这种定义数组的方式,这可能是因为早期某些计算机读物的误导,从现在开始最好就不要再使用这种糟糕的方式了。

提示:Java 的模仿者 C# 就不再支持type arrayName[]这种语法,它只支持第一种定义数组的语法。越来越多的语言不再支持type arrayName[]这种数组定义语法。

以上两种格式都可以声明一个数组,其中的数据类型既可以是基本数据类型,也可以是引用数据类型。数组名可以是任意合法的变量名。声明数组就是要告诉计算机该数组中数据的类型是什么。例如:

int[] score;    // 存储学生的成绩,类型为整型
double[] price;    // 存储商品的价格,类型为浮点型
String[] name;    // 存储商品名称,类型为字符串型

在声明数组时不需要规定数组的长度,例如:

int score[10];    // 这是错误的

注意:在声明数组变量时千万不要漏写[]。

分配空间

声明了数组,只是得到了一个存放数组的变量,并没有为数组元素分配内存空间,不能使用。因此要为数组分配内存空间,这样数组的每一个元素才有一个空间进行存储。

简单地说,分配空间就是要告诉计算机在内存中为它分配几个连续的位置来存储数据。在 Java 中可以使用 new 关键字来给数组分配空间。分配空间的语法格式如下:

arrayName = new type[size];    // 数组名 = new 数据类型[数组长度];

其中,数组长度就是数组中能存放的元素个数,显然应该为大于 0 的整数,例如:

score = new int[10];
price = new double[30];
name = new String[20];

这里的 score 是已经声明过的 int[] 类型的变量,当然也可以在声明数组时就给它分配空间,语法格式如下:

type[] arrayName = new type[size];    // 数据类型[] 数组名 = new 数据类型[数组长度];

例如,声明并分配一个长度为 5 的 int 类型数组 arr,代码如下:

int[] arr = new int[5];

执行后 arr 数组在内存中的格式如图所示。

img

在图 中 arr 为数组名称,方括号“[]”中的值为数组的下标。数组通过下标来区分数组中不同的元素,并且下标是从 0 开始的。因此这里包含 5 个元素的 arr 数组最大下标为 4。

注意:一旦声明了数组的大小,就不能再修改。这里的数组长度也是必需的,不能少。

尽管数组可以存储一组基本数据类型的元素,但是数组整体属于引用数据类型。当声明一个数组变量时,其实是创建了一个类型为“数据类型[]”(如 int[]、double[]、String[])的数组对象,它具有表所示的方法和属性。

方法 名称 返回值
clone() Object
equals(Object obj) boolean
getClass() Class<?>
hashCode() int
notify() void
notify All() void
toString() String
wait() void
wait(long timeout) void
wait(long timeout,int nanos) void
属性 length int

初始化一维数组

Java 语言中数组必须先初始化,然后才可以使用。所谓初始化,就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值。

能不能只分配内存空间,不赋初始值呢?

不行,一旦为数组的每个数组元素分配了内存空间,每个内存空间里存储的内容就是该数组元素的值,即使这个内存空间存储的内容为空,这个空也是一个值(null)。不管以哪种方式来初始化数组,只要为数组元素分配了内存空间,数组元素就具有了初始值。初始值的获得有两种形式,一种由系统自动分配,另一种由程序员指定。

数组在初始化数组的同时,可以指定数组的大小,也可以分别初始化数组中的每一个元素。在 Java 语言中,初始化数组有 3 种方式。

使用 new 指定数组大小后进行初始化

使用 new 关键字创建数组,在创建时指定数组的大小。语法如下:

type[] arrayName = new int[size];

创建数组之后,元素的值并不确定,需要为每一个数组的元素进行赋值,其下标从 0 开始。

例:创建包含 5 个元素的 int 类型的数组,然后分别将元素的值设置为 1、2、3、5 和 8。代码如下:

int[] number = new int[5];
number[0] = 1;
number[1] = 2
;number[2] = 3;
number[3] = 5;
number[4] = 8;

如果程序员只指定了数组的长度,那么系统将负责为这些数组元素分配初始值。指定初始值时,系统按如下规则分配初始值。

  • 数组元素的类型是基本类型中的整数类型(byte、short、int 和 long),则数组元素的值是 0。
  • 数组元素的类型是基本类型中的浮点类型(float、double),则数组元素的值是 0.0。
  • 数组元素的类型是基本类型中的字符类型(char),则数组元素的值是‘\u0000’。
  • 数组元素的类型是基本类型中的布尔类型(boolean),则数组元素的值是 false。
  • 数组元素的类型是引用类型(类、接口和数组),则数组元素的值是 null。

使用 new 指定数组元素的值

使用上述方式初始化数组时,只有在为元素赋值时才确定值。可以不使用上述方式,而是在初始化时就已经确定值。语法如下:

type[] arrayName = new type[]{值 1,值 2,值 3,值 4,• • •,值 n};

例 :使用 new 直接指定数组元素的值。代码如下:

int[] number = new int[]{1, 2, 3, 5, 8};

注意:不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值,这样会造成代码错误。例如下面代码:

int[] number = new int [5] {1,2,3,4,5};

直接指定数组元素的值

在上述两种方式的语法中,type 可以省略,如果已经声明数组变量,那么直接使用这两种方式进行初始化。如果不想使用上述两种方式,那么可以不使用 new 直接指定数组元素的值。语法如下:

type[] arrayName = {值 1,值 2,值 3,...,值 n};

例:在前面例子的基础上更改代码,直接使用上述语法实现 number 数组的初始化。代码如下:

int[] number = {1,2,3,5,8};

使用这种方式时,数组的声明和初始化操作要同步,即不能省略数组变量的类型。如下的代码就是错误的:

int[] number;number = {1,2,3,5,8};

数组的访问

获取单个元素

获取单个元素是指获取数组中的一个元素,如第一个元素或最后一个元素。获取单个元素的方法非常简单,指定元素所在数组的下标即可。语法如下:

arrayName[index];

其中,arrayName 表示数组变量,index 表示下标,下标为 0 表示获取第一个元素,下标为 array.length-1 表示获取最后一个元素。当指定的下标值超出数组的总长度时,会拋出 ArraylndexOutOfBoundsException 异常。

例:获取 number 数组中的第一个元素、最后一个元素和第六个元素,并将元素的值输出。代码如下:

int[] number = {1,2,3,5,8};
System.out.println("获取第一个元素:"+number[0]);
System.out.println("获取最后一个元素:"+number[number.length-1]);
System.out.println("获取第6个元素:"+number[5]);

执行上述代码,输出结果如下所示:

获取第一个元素:1
获取最后一个元素:8
java.lang.ArrayIndexOutOfBoundsException: 5

我们一共存入了 5 个值,所以下标的取值为 0~4。因为 number[5] 取出的内容超过了这个下标,所以输出结果会提示数组索引超出绑定异常(ArrayIndexOutOfBoundsException)。这一点是在使用数组中是经常出现的问题,大家在编写程序时应该引起注意。

例:编写一个 Java 程序,使用数组存放录入的 5 件商品价格,然后使用下标访问第 3 个元素的值。

import java.util.Scanner;
public class Test06 {    
    public static void main(String[] args) {        
        int[] prices = new int[5]; // 声明数组并分配空间        
        Scanner input = new Scanner(System.in); // 接收用户从控制台输入的数据       
        for (int i = 0; i < prices.length; i++) {            
            System.out.println("请输入第" + (i + 1) + "件商品的价格:");            
            prices[i] = input.nextInt(); // 接收用户从控制台输入的数据        
        }        
        System.out.println("第 3 件商品的价格为:" + prices[2]);    
    }
}

上述代码的“int[] prices = new int[5]”语句创建了需要 5 个元素空间的 prices 数组,然后结合 for 循环向数组中的每个元素赋值。

注意:在 Java 中取得数组的长度(也就是数组元素的长度)可以利用“数组名称.length”,返回一个 int 型数据。

数组的索引从 0 开始,而 for 循环中的变量 i 也从 0 开始,因此 score 数组中的元素可以使用 scored 来表示,大大简化了代码。最后使用 prices[2] 获取 prices 数组的第 3 个元素,最终运行效果如下所示。

请输入第1件商品的价格:
5
请输入第2件商品的价格:
4
请输入第3件商品的价格:
6
请输入第4件商品的价格:
4
请输入第5件商品的价格:
8
第 3 件商品的价格为:6

获取全部元素

当数组中的元素数量不多时,要获取数组中的全部元素,可以使用下标逐个获取元素。但是,如果数组中的元素过多,再使用单个下标则显得烦琐,此时使用一种简单的方法可以获取全部元素——使用循环语句。

下面利用 for 循环语句遍历 number 数组中的全部元素,并将元素的值输出。代码如下:

int[] number = {1,2,3,5,8};
for (int i=0;i<number.length;i++) {    
    System.out.println("第"+(i+1)+"个元素的值是:"+number[i]);
}

除了使用 for 语句,还可以使用 foreach 遍历数组中的元素,并将元素的值输出。代码如下:

for(int val:number) {    
    System.out.print("元素的值依次是:"+val+"\t");
}

二维数组(了解即可)

为了方便组织各种信息,计算机常将信息以表的形式进行组织,然后再以行和列的形式呈现出来。二维数组的结构决定了其能非常方便地表示计算机中的表,以第一个下标表示元素所在的行,第二个下标表示元素所在的列。

创建二维数组

在 Java 中二维数组被看作数组的数组,即二维数组为一个特殊的一维数组,其每个元素又是一个一维数组。Java 并不直接支持二维数组,但是允许定义数组元素是一维数组的一维数组,以达到同样的效果。声明二维数组的语法如下:

type arrayName[][];    // 数据类型 数组名[][];  
//或   
type[][] arrayName;    // 数据类型[][] 数组名;

其中,type 表示二维数组的类型,arrayName 表示数组名称,第一个中括号表示行,第二个中括号表示列。

下面分别声明 int 类型和 char 类型的数组,代码如下:

int[][] age;char[][] sex;

初始化二维数组

二维数组可以初始化,和一维数组一样,可以通过 3 种方式来指定元素的初始值。这 3 种方式的语法如下:

type[][] arrayName = new type[][]{值 1,值 2,值 3,…,值 n};    // 在定义时初始化
type[][] arrayName = new type[size1][size2];    // 给定空间,在赋值
type[][] arrayName = new type[size][];    // 数组第二维长度为空,可变化

例:使用第一种方式声明 int 类型的二维数组,然后初始化该二维数组。代码如下:

int[][] temp = new int[][]{{1,2},{3,4}};

上述代码创建了一个二行二列的二维数组 temp,并对数组中的元素进行了初始化。图所示为该数组的内存结构。

img

使用第二种方式声明 int 类型的二维数组,然后初始化该二维数组。代码如下:

int[][] temp = new int[2][2];

使用第三种方式声明 int 类型的二维数组,并且初始化数组。代码如下:

int[][] temp = new int[2][];

获取单个元素

在上部分使用的前 2 种方式创建并初始化了一个二行二列的 int 类型数组 temp。当需要获取二维数组中元素的值时,也可以使用下标来表示。语法如下:

arrayName[i-1][j-1];

其中,arrayName 表示数组名称,i 表示数组的行数,j 表示数组的列数。例如,要获取第二行第二列元素的值,应该使用 temp[1][1]来表示。这是由于数组的下标起始值为 0,因此行和列的下标需要减 1

例:通过下标获取 class_score 数组中第二行第二列元素的值与第四行第一列元素的值。代码如下:

public static void main(String[] args) {    
    double[][] class_score = {{10.0,99,99},{100,98,97},{100,100,99.5},{99.5,99,98.5}};    
    System.out.println("第二行第二列元素的值:"+class_score[1][1]);   
    System.out.println("第四行第一列元素的值:"+class_score[3][0]);
}

执行上述代码,输出结果如下:

第二行第二列元素的值:98.0
第四行第一列元素的值:99.5

获取全部元素

在一维数组中直接使用数组的 length 属性获取数组元素的个数。而在二维数组中,直接使用 length 属性获取的是数组的行数,在指定的索引后加上 length(如 array[0].length)表示的是该行拥有多少个元素,即列数

如果要获取二维数组中的全部元素,最简单、最常用的办法就是使用 for 语句。在一维数组全部输出时,我们使用一层 for 循环,而二维数组要想全部输出,则使用嵌套 for 循环(2 层 for 循环)。

例 :使用 for 循环语句遍历 double 类型的 class_score 数组的元素,并输出每一行每一列元素的值。代码如下:

public static void main(String[] args) {    
    double[][] class_score = { { 100, 99, 99 }, { 100, 98, 97 }, { 100, 100, 99.5 }, { 99.5, 99, 98.5 } };   
    for (int i = 0; i < class_score.length; i++) { // 遍历行        
        for (int j = 0; j < class_score[i].length; j++) {           
            System.out.println("class_score[" + i + "][" + j + "]=" + class_score[i][j]);        
        }    
    }
}

上述代码使用嵌套 for 循环语句输出二维数组。在输出二维数组时,第一个 for 循环语句表示以行进行循环,第二个 for 循环语句表示以列进行循环,这样就实现了获取二维数组中每个元素的值的功能。

执行上述代码,输出结果如下所示。

class_score[0][0]=100.0
class_score[0][1]=99.0
class_score[0][2]=99.0
class_score[1][0]=100.0
class_score[1][1]=98.0
class_score[1][2]=97.0
class_score[2][0]=100.0
class_score[2][1]=100.0
class_score[2][2]=99.5
class_score[3][0]=99.5
class_score[3][1]=99.0
class_score[3][2]=98.5

例:假设有一个矩阵为 5 行 5 列,该矩阵是由程序随机产生的 10 以内数字排列而成。下面使用二维数组来创建该矩阵,代码如下:

public class Test11 {    
    public static void main(String[] args) {        
        // 创建一个二维矩阵        
        int[][] matrix = new int[5][5];        
        // 随机分配值       
        for (int i = 0; i < matrix.length; i++) {            
            for (int j = 0; j < matrix[i].length; j++) {                
                matrix[i][j] = (int) (Math.random() * 10);           
            }       
        }        
        System.out.println("下面是程序生成的矩阵\n");        
        // 遍历二维矩阵并输出        
        for (int k = 0; k < matrix.length; k++) {            
            for (int g = 0; g < matrix[k].length; g++) {                
                System.out.print(matrix[k][g] + "");            
            }            
            System.out.println();        
        }   
    }
}

在该程序中,首先定义了一个二维数组,然后使用两个嵌套的 for 循环向二维数组中的每个元素赋值。其中,Math.random() 方法返回的是一个 double 类型的数值,数值为 0.6、0.9 等,因此乘以 10 之后为 10 以内的整数。最后又使用了两个嵌套的 for 循环遍历二维数组,输出二维数组中的值,从而产生矩阵。

运行该程序的结果如下所示。

下面是程序生成的矩阵

78148
69230
43823
75663
05688

for each 循环语句不能自动处理二维数组的每一个元素。它是按照行, 也就是一维数组处理的。要想访问二维教组 a 的所有元素, 需要使用两个嵌套的循环, 如下所示:

for (double[] row : a) {    
    for (double value : row) {        
        ......    
    }
}

使用 for each 循环语句输出,代码如下所示:

public static void main(String[] args) {    
    double[][] class_score = { { 100, 99, 99 }, { 100, 98, 97 }, { 100, 100, 99.5 }, { 99.5, 99, 98.5 } };    
    for (double[] row : class_score) {        
        for (double value : row) {            
            System.out.println(value);       
        }   
    }
}

输出结果为:

100.0
99.0
99.0
100.0
98.0
97.0
100.0
100.0
99.5
99.5
99.0
98.5

提示:要想快速地打印一个二维数组的数据元素列表,可以调用:

System.out.println(Arrays.deepToString(arrayName));

代码如下:

System.out.println(Arrays.deepToString(class_score));

输出格式为:

[[100.0, 99.0, 99.0], [100.0, 98.0, 97.0], [100.0, 100.0, 99.5], [99.5, 99.0, 98.5]]

获取整行元素

除了获取单个元素和全部元素之外,还可以单独获取二维数组的某一行中所有元素的值,或者二维数组中某一列元素的值。获取指定行的元素时,需要将行数固定,然后只遍历该行中的全部列即可。

例 编写一个案例,接收用户在控制台输入的行数,然后获取该行中所有元素的值。代码如下:

public static void main(String[] args) {
    double[][] class_score = { { 100, 99, 99 }, { 100, 98, 97 }, { 100, 100, 99.5 }, { 99.5, 99, 98.5 } };
    Scanner scan = new Scanner(System.in);
    System.out.println("当前数组只有" + class_score.length + "行,您想查看第几行的元素?请输入:");
    int number = scan.nextInt();
    for (int j = 0; j < class_score[number - 1].length; j++) {
        System.out.println("第" + number + "行的第[" + j + "]个元素的值是:" + class_score[number - 1][j]);
    }
}

执行上述代码进行测试,输出结果如下所示。

当前数组只有4行,您想查看第几行的元素?请输入:
3
第3行的第[0]个元素的值是:100.0
第3行的第[1]个元素的值是:100.0
第3行的第[2]个元素的值是:99.5

获取整列元素

获取指定列的元素与获取指定行的元素相似,保持列不变,遍历每一行的该列即可。

例:编写一个案例,接收用户在控制台中输入的列数,然后获取二维数组中所有行中该列的值。代码如下:

public static void main(String[] args) {
    double[][] class_score = { { 100, 99, 99 }, { 100, 98, 97 }, { 100, 100, 99.5 }, { 99.5, 99, 98.5 } };
    Scanner scan = new Scanner(System.in);
    System.out.println("您要获取哪一列的值?请输入:");
    int number = scan.nextInt();
    for (int i = 0; i < class_score.length; i++) {
        System.out.println("第 " + (i + 1) + " 行的第[" + number + "]个元素的值是" + class_score[i][number]);
    }
}

执行上述代码进行测试,如下所示。

您要获取哪一列的值?请输入:
2
第 1 行的第[2]个元素的值是99.0
第 2 行的第[2]个元素的值是97.0
第 3 行的第[2]个元素的值是99.5
第 4 行的第[2]个元素的值是98.5

多维数组(了解即可)

除了一维数组和二维数组外,Java 中还支持更多维的数组,如三维数组、四维数组和五维数组等,它们都属于多维数组。经过前面一维,二维的练习后不难发现,想要提高数组的维数,只要在声明数组时将索引与中括号再加一组即可,所以三维数组的声明为 int score[][][],而四维数组为 int score[][][][],以此类推。

通常也将二维数组看作是多维数组。

三维数组有三个层次,可以将三维数组理解为一个一维数组,其内容的每个元素都是二维数组。依此类推,可以获取任意维数的数组。

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组,多维数组的声明、初始化和使用都与二维数组相似。

例:假设程序中有一个名为 namelist 的 String 类型三维数组,下面编写代码对它进行遍历,输出每个元素的值。代码如下:

public static void main(String[] args) {
    String[][][] namelist = { { { "张阳", "李风", "陈飞" }, { "乐乐", "飞飞", "小曼" } },
            { { "Jack", "Kimi" }, { "Lucy", "Lily", "Rose" } }, { { "徐璐璐", "陈海" }, { "李丽丽", "陈海清" } } };
    for (int i = 0; i < namelist.length; i++) {
        for (int j = 0; j < namelist[i].length; j++) {
            for (int k = 0; k < namelist[i][j].length; k++) {
                System.out.println("namelist[" + i + "][" + j + "][" + k + "]=" + namelist[i][j][k]);
            }
        }
    }
}

执行上述代码,输出结果如下所示。

namelist[0][0][0]=张阳
namelist[0][0][1]=李风
namelist[0][0][2]=陈飞
namelist[0][1][0]=乐乐
namelist[0][1][1]=飞飞
namelist[0][1][2]=小曼
namelist[1][0][0]=Jack
namelist[1][0][1]=Kimi
namelist[1][1][0]=Lucy
namelist[1][1][1]=Lily
namelist[1][1][2]=Rose
namelist[2][0][0]=徐璐璐
namelist[2][0][1]=陈海
namelist[2][1][0]=李丽丽
namelist[2][1][1]=陈海清

不规则数组

Java 实际上没有多维数组,只有一维数组。多维数组被解释为是数组的数组,所以因此会衍生出一种不规则数组

规则的 4×3 二维数组有 12 个元素,而不规则数组就不一定了。如下代码静态初始化了一个不规则数组。

int intArray[][] = {{1,2}, {11}, {21,22,23}, {31,32,33}};

高维数组(二维以及二维以上的数组称为高维数组)是 4 个元素,但是低维数组元素个数不同,如图所示,其中第 1 个数组有两个元素,第 2 个数组有 1 个元素,第 3 个数组有 3 个元素,第 4 个数组有 3 个元素。这就是不规则数组。

不规则数组

动态初始化不规则数组比较麻烦,不能使用 new int[4][3] 语句,而是先初始化高维数组,然后再分别逐个初始化低维数组。

代码如下:

int intArray[][] = new int[4][]; //先初始化高维数组为4
// 逐一初始化低维数组
intArray[0] = new int[2];
intArray[1] = new int[1];
intArray[2] = new int[3];
intArray[3] = new int[3];

从上述代码初始化数组完成之后,不是有 12 个元素而是 9 个元素,它们的下标索引如图所示,可见其中下标 [0][2]、[1][1][1][2] 是不存在的,如果试图访问它们则会抛出下标越界异常。

不规则数组访问

提示:下标越界异常(ArrayIndexOutOfBoundsException)是试图访问不存在的下标时引发的。例如一个一维 array 数组如果有 10 个元素,那么表达式 array[10] 就会发生下标越界异常,这是因为数组下标是从 0 开始的,最后一个元素下标是数组长度减 1,所以 array[10] 访问的元素是不存在的。

下面介绍一个不规则数组的示例:

import java.util.Arrays;

public class HelloWorld {
    public static void main(String[] args) {
        int intArray[][] = new int[4][]; // 先初始化高维数组为4
        // 逐一初始化低维数组
        intArray[0] = new int[2];
        intArray[1] = new int[1];
        intArray[2] = new int[3];
        intArray[3] = new int[3];
        // for循环遍历
        for (int i = 0; i < intArray.length; i++) {
            for (int j = 0; j < intArray[i].length; j++) {
                intArray[i][j] = i + j;
            }
        }
        // for-each循环遍历
        for (int[] row : intArray) {
            for (int column : row) {
                System.out.print(column);
                // 在元素之间添加制表符,
                System.out.print('\t');
            }
            // 一行元素打印完成后换行
            System.out.println();
        }
        System.out.println(intArray[0][2]); // 发生运行期错误
    }
}

不规则数组访问和遍历可以使用 for 和 for-each 循环,但要注意下标越界异常发生

上述代码第 18 行和第 19 行采用 for-each 循环遍历不规则数组,其中代码第 18 行 for-each 循环取出的数据是 int 数组,所以 row 类型是 int[]。代码第 19 行 for-each 循环取出的数据是 int 数据,所以 column 的类型 int。另外,注意代码第 27 行试图访问 intArray[0][2]元素,由于 [0][2] 不存在所以会发生下标越界异常。

Arrays 类

Arrays 类是一个工具类,其中包含了数组操作的很多方法。这个 Arrays 类里均为 static 修饰的方法(static 修饰的方法可以直接通过类名调用),可以直接通过 Arrays.xxx(xxx) 的形式调用方法。

Arrays类常用方法

int binarySearch(type[] a, type key)

使用二分法查询 key 元素值在 a 数组中出现的索引,如果 a 数组不包含 key 元素值,则返回负数。调用该方法时要求数组中元素己经按升序排列,这样才能得到正确结果。

int binarySearch(type[] a, int fromIndex, int toIndex, type key)

这个方法与前一个方法类似,但它只搜索 a 数组中 fromIndex 到 toIndex 索引的元素。调用该方法时要求数组中元素己经按升序排列,这样才能得到正确结果。

type[] copyOf(type[] original, int length)

这个方法将会把 original 数组复制成一个新数组,其中 length 是新数组的长度。如果 length 小于 original 数组的长度,则新数组就是原数组的前面 length 个元素,如果 length 大于 original 数组的长度,则新数组的前面元索就是原数组的所有元素,后面补充 0(数值类型)、false(布尔类型)或者 null(引用类型)。

type[] copyOfRange(type[] original, int from, int to)

这个方法与前面方法相似,但这个方法只复制 original 数组的 from 索引到 to 索引的元素。

boolean equals(type[] a, type[] a2)

如果 a 数组和 a2 数组的长度相等,而且 a 数组和 a2 数组的数组元素也一一相同,该方法将返回 true。

void fill(type[] a, type val)

该方法将会把 a 数组的所有元素都赋值为 val。

void fill(type[] a, int fromIndex, int toIndex, type val)

该方法与前一个方法的作用相同,区别只是该方法仅仅将 a 数组的 fromIndex 到 toIndex 索引的数组元素赋值为 val。

void sort(type[] a)

该方法对 a 数组的数组元素进行排序。

void sort(type[] a, int fromIndex, int toIndex)

该方法与前一个方法相似,区别是该方法仅仅对 fromIndex 到 toIndex 索引的元素进行排序。

String toString(type[] a)

该方法将一个数组转换成一个字符串。该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号,和空格隔开。

下面程序示范了 Arrays 类的用法。

public class ArraysTest {
    public static void main(String[] args) {
        // 定义一个a数组
        int[] a = new int[] { 3, 4, 5, 6 };
        // 定义一个a2数组
        int[] a2 = new int[] { 3, 4, 5, 6 };
        // a数组和a2数组的长度相等,毎个元素依次相等,将输出true
        System.out.println("a数组和a2数组是否相等:" + Arrays.equals(a, a2));
        // 通过复制a数组,生成一个新的b数组
        int[] b = Arrays.copyOf(a, 6);
        System.out.println("a数组和b数组是否相等:" + Arrays.equals(a, b));
        // 输出b数组的元素,将输出[3, 4, 5, 6, 0, 0]
        System.out.println("b 数组的元素为:" + Arrays.toString(b));
        // 将b数组的第3个元素(包括)到第5个元素(不包括)賦值为1
        Arrays.fill(b, 2, 4, 1);
        // 输出b数组的元素,将输出[3, 4, 1, 1, 0, 0]
        System.out.println("b 数组的元素为:" + Arrays.toString(b));
        // 对b数组进行排序
        Arrays.sort(b);
        // 输出b数组的元素.将输出[0,0,1,1,3,4]
        System.out.println("b数组的元素为:" + Arrays.toString(b));
    }
}

Arrays 类处于 java.util 包下,为了在程序中使用 Arrays 类,必须在程序中导入 java.util.Arrays 类。

除此之外,在 System 类里也包含了一个static void arraycopy(Object src, int srePos, Object dest, int dcstPos, int length)方法,该方法可以将 src 数组里的元素值赋给 dest 数组的元素,其中 srcPos 指定从 src 数组的第几个元素开始赋值,length 参数指定将 src 数组的多少个元素值赋给 dest 数组的元素。

Arrays类java8新增方法

Java 8 增强了 Arrays 类的功能,为 Arrays 类增加了一些工具方法,这些工具方法可以充分利用多 CPU 并行的能力来提高设值、排序的性能。

提示:由于计算机硬件的飞速发展,目前几乎所有家用 PC 都是 4 核、8 核的 CPU,而服务器的 CPU 则具有更好的性能,因此 Java 8 与时俱进地增加了并发支持,并发支持可以充分利用硬件设备来提高程序的运行性能。

oid 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 spliterator(xxx[] array)

将该数组的所有元素转换成对应的 Spliterator 对象。

Spliterator.OfXxx spliterator(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)

该方法与上一个方法相似,区别是该方法仅将 fromIndex 到 toIndex 索引的元索转换为 Stream。

上面方法列表中,所有以 parallel 开头的方法都表示该方法可利用 CPU 并行的能力来提高性能。上面方法中的 xxx 代表不同的数据类型,比如处理 int[] 型数组时应将 xxx 换成 int,处理 long[] 型数组时应将 XXX 换成 long。

下面程序示范了 Java 8 为 Arrays 类新增的方法。

下面程序用到了接口、匿名内部类的知识,读者阅读起来可能有一定的困难,此处只要大致知道 Arrays 新增的这些新方法就行,暂时并不需要读者立即掌握该程序。

public class ArraysTest2 {
    public static void main(String[] args) {

        int[] arr1 = new int[] { 3, 4, 25, 16, 30, 18 };
        // 对数组arr1进行并发排序
        Arrays.parallelSort(arr1);
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = new int[] { 13, -4, 25, 16, 30, 18 };
        Arrays.parallelPrefix(arr2, new IntBinaryOperator() {

            // left 代表数组中前一个索引处的元素,计算第一个元素时,left为1
            // right代表数组中当前索引处的元素
            public int applyAsInt(int left, int right) {
                return left * right;
            }
        });
        System.out.println(Arrays.toString(arr2));
        int[] arr3 = new int[5];
        Arrays.parallelSetAll(arr3, new IntUnaryOperator() {
            // operand代表正在计算的元素索引
            public int applyAsInt(int operand) {
                return operand * 5;
            }
        });
        System.out.println(Arrays.toString(arr3));
    }
}

上面程序中第一行粗体字代码调用了 parallelSort() 方法对数组执行排序,该方法的功能与传统 sort() 方法大致相似,只是在多 CPU 机器上会有更好的性能。

第二段粗体字代码使用的计算公式为 left * right,其中 left 代表数组中当前一个索引处的元素,right 代表数组中当前索引处的元素。程序使用的数组为:

{3, -4 , 25, 16, 30, 18)

计算新的数组元素的方式为:

{1*3=3, 3*-4—12, -12*25=-300, -300*16=—48000, -48000*30=—144000, -144000*18=-2592000}

因此将会得到如下新的数组元素:

{3, -12, -300, -4800, -144000, -2592000)

第三段粗体字代码使用 operand * 5 公式来设置数组元素,该公式中 operand 代表正在计算的数组元素的索引。因此第三段粗体字代码计算得到的数组为:

{0, 5, 10, 15, 20}

提示:上面两段粗体字代码都可以使用 Lambda 表达式进行简化。

数组的操作

数组比较

数组相等的条件不仅要求数组元素的个数必须相等,而且要求对应位置的元素也相等。Arrays 类提供了 equals() 方法比较整个数组。

语法如下:

Arrays.equals(arrayA, arrayB);

其中,arrayA 是用于比较的第一个数组,arrayB 是用于比较的第二个数组。

例 :下面代码演示 Arrays 类的 equals() 方法的使用。

public static void main(String[] args) {
    double[] score1 = { 99, 100, 98.5, 96.5, 72 };
    double[] score2 = new double[5];
    score2[0] = 99;
    score2[1] = 100;
    score2[2] = 98.5;
    score2[3] = 96.5;
    score2[4] = 72;
    double[] score3 = { 99, 96.5, 98.5, 100, 72 };
    if (Arrays.equals(score1, score2)) {
        System.out.println("score1 数组和 score2 数组相等");
    } else {
        System.out.println("score1 数组和 score2 数组不等");
    }
    if (Arrays.equals(score1, score3)) {
        System.out.println("score1 数组和 score3 数组相等");
    } else {
        System.out.println("score1 数组和 score3 数组不等");
    }
}

上述代码中定义 3 个数组,分别为 score1、score2 和 score3。第一个数组直接给出了数组的值;第二个数组先定义数组的长度,然后为每个元素赋值;第三个数组中的元素和第一个数组中的元素相同,但是顺序不同。分别将 score1 数组与 score2 和 score3 数组进行比较,并输出比较的结果。

运行上述代码,输出结果如下:

score1 数组和 score2 数组相等
score1 数组和 score3 数组不等

Java数组填充

Arrays 类提供了一个 fill() 方法,可以在指定位置进行数值填充。fill() 方法虽然可以填充数组,但是它的功能有限制,只能使用同一个数值进行填充。语法如下:

Arrays.fill(array,value);

其中,array 表示数组,value 表示填充的值。

例 声明一个 int 类型的 number 数组,然后通过 for 语句进行遍历,在该语句中调用 Arrays 类的 fill() 方法来填充数组,并输出数组中元素的值。代码如下:

public static void main(String[] args) {    
    int[] number = new int[5];    
    System.out.println("number —共有 " + number.length + " 个元素,它们分别是:");    
    for (int i = 0; i < number.length; i++) {        
        Arrays.fill(number, i);        
        System.out.println("number[" + i + "]=" + i);    
    }
}

执行上述代码,输出结果如下所示。

number 一共有 5 个元素,它们分别是:
number[0]=0
number[1]=1
number[2]=2
number[3]=3
number[4]=4

注意:在向数组中填充数组元素时要注意,虽然定义的数组长度为 6,但是数组的下标是从 0 开始的,因此数组的最大下标值应该是 5,此时如果为下标为 6 的数组元素赋值,则会出现数组下标越界异常。

数组查找

查找数组是指从数组中查询指定位置的元素,或者查询某元素在指定数组中的位置。使用 Arrays 类的 binarySearch() 方法可以实现数组的查找,该方法可使用二分搜索法来搜索指定数组,以获得指定对象,该方法返回要搜索元素的索引值。

binarySearch() 方法有多种重载形式来满足不同类型数组的查找需要,常用的重载形式有两种。

(1) 第一种形式如下:

binarySearch(Object[] a,Object key);

其中,a 表示要搜索的数组,key 表示要搜索的值。如果 key 包含在数组中,则返回搜索值的索引;否则返回 -1 或“-插入点”。插入点指搜索键将要插入数组的位置,即第一个大于此键的元素索引。

在进行数组查询之前,必须对数组进行排序(可以使用 sort() 方法)。如果没有对数组进行排序,则结果是不确定的。如果数组包含多个带有指定值的元素,则无法确认找到的是哪一个。

例:声明 double 类型的 score 数组,接着调用 Arrays 类的 sort() 方法对 score 数组排序,排序后分别查找数组中值为 100 和 60 的元素,分别将结果保存到 index1 和 index2 变量中,最后输出变量的值。代码如下:

public static void main(String[] args) {    
    double[] score = { 99.5, 100, 98, 97.5, 100, 95, 85.5, 100 };    
    Arrays.sort(score);    int index1 = Arrays.binarySearch(score, 100);    
    int index2 = Arrays.binarySearch(score, 60);    
    System.out.println("查找到 100 的位置是:" + index1);   
    System.out.println("查找到 60 的位置是:" + index2);
}

执行上述代码,输出结果如下:

查找到 100 的位置是:5
查找到 60 的位置是:-1

(2) 除了上述形式外,binarySearch() 还有另一种常用的形式,这种形式用于在指定的范围内查找某一元素。语法如下:

binarySearch(Object[] a,int fromIndex,int toIndex,Object key);

其中,a 表示要进行查找的数组,fromIndex 指定范围的开始处索引(包含开始处),toIndex 指定范围的结束处索引(不包含结束处),key 表示要搜索的元素。

在使用 binarySearch() 方法的上述重载形式时,也需要对数组进行排序,以便获取准确的索引值。如果要查找的元素 key 在指定的范围内,则返回搜索键的索引;否则返回 -1 或 “-插入点”。插入点指要将键插入数组的位置,即范围内第一个大于此键的元素索引。

例创建的 score 数组进行查找元素,指定开始位置为 2,结束位置为 6。代码如下:

public static void main(String[] args) {    
    double[] score = {99.5,100,98,97.5,100,95,85.5,100};    
    Arrays.sort(score);    
    int index1 = Arrays.binarySearch(score,2,6,100);    
    int index2 = Arrays.binarySearch(score,2,6,60);    
    System.out.println("查找到 100 的位置是:"+index1);    
    System.out.println("查找到 60 的位置是:"+ index2);
}

执行上述代码,输出结果如下:

查找到 100 的位置是:5
查找到 60 的位置是:-3

注意:实现对数组进行查找的方法很多,但是使用 Arrays 对象的 binarySearch() 方法是最简单、最方便,因此该方法经常被应用。

数组复制

所谓复制数组,是指将一个数组中的元素在另一个数组中进行复制。

在 Java 中实现数组复制分别有以下 4 种方法:

  1. Arrays 类的 copyOf() 方法
  2. Arrays 类的 copyOfRange() 方法
  3. System 类的 arraycopy() 方法
  4. Object 类的 clone() 方法

使用 copyOf() 方法和 copyOfRange() 方法

Arrays 类的 copyOf() 方法与 copyOfRange() 方法都可实现对数组的复制。copyOf() 方法是复制数组至指定长度,copyOfRange() 方法则将指定数组的指定长度复制到一个新数组中。

使用 copyOf() 方法对数组进行复制

Arrays 类的 copyOf() 方法的语法格式如下:

Arrays.copyOf(dataType[] srcArray,int length);

其中,srcArray 表示要进行复制的数组,length 表示复制后的新数组的长度。

使用这种方法复制数组时,默认从原数组的第一个元素(索引值为 0)开始复制,目标数组的长度将为 length。如果 length 大于 srcArray.length,则目标数组中采用默认值填充;如果 length 小于 srcArray.length,则复制到第 length 个元素(索引值为 length-1)即止。

注意:目标数组如果已经存在,将会被重构。

例 :假设有一个数组中保存了 5 个成绩,现在需要在一个新数组中保存这 5 个成绩,同时留 3 个空余的元素供后期开发使用。

使用 Arrays 类的 CopyOf() 方法完成数组复制的代码如下:

import java.util.Arrays;
public class Test19{
    public static void main(String[] args) {
        // 定义长度为 5 的数组
        int scores[] = new int[]{57,81,68,75,91};

        // 输出原数组
        System.out.println("原数组内容如下:");

        // 循环遍历原数组
        for(int i=0;i<scores.length;i++) {
            // 将数组元素输出
            System.out.print(scores[i]+"\t");
        }

        // 定义一个新的数组,将 scores 数组中的 5 个元素复制过来
        // 同时留 3 个内存空间供以后开发使用
        int[] newScores = (int[])Arrays.copyOf(scores,8);
        System.out.println("\n复制的新数组内容如下:");

        // 循环遍历复制后的新数组
        for(int j=0;j<newScores.length;j++) {
            // 将新数组的元素输出
            System.out.print(newScores[j]+"\t");
        }
    }
}

在上述代码中,由于原数组 scores 的长度为 5,而要复制的新数组 newScores 的长度为 8,因此在将原数组中的 5 个元素复制完之后,会采用默认值填充剩余 3 个元素的内容。

因为原数组 scores 的数据类型为 int,而使用 Arrays.copyOf(scores,8) 方法复制数组之后返回的是 Object[] 类型,因此需要将 Object[] 数据类型强制转换为 int[] 类型。同时,也正因为 scores 的数据类型为 int,因此默认值为 0。

运行的结果如下所示。

原数组内容如下:
57    81    68    75    91   
复制的新数组内容如下:
57    81    68    75    91    0    0    0

使用 CopyOfRange() 方法对数组进行复制

Arrays 类的 CopyOfRange() 方法是另一种复制数组的方法,其语法形式如下:

Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex)

其中:

  • srcArray 表示原数组。
  • startIndex 表示开始复制的起始索引,目标数组中将包含起始索引对应的元素,另外,startIndex 必须在0到 srcArray.length 之间。
  • endIndex 表示终止索引,目标数组中将不包含终止索引对应的元素,endIndex 必须大于等于 startIndex,可以大于 srcArray.length,如果大于 srcArray.length,则目标数组中使用默认值填充。

注意:目标数组如果已经存在,将会被重构。

例:假设有一个名称为 scores 的数组其元素为 8 个,现在需要定义一个名称为 newScores 的新数组。新数组的元素为 scores 数组的前 5 个元素,并且顺序不变。

使用 Arrays 类 copyOfRange() 方法完成数组复制的代码如下:

public class Test20 {
    public static void main(String[] args) {
        // 定义长度为8的数组
        int scores[] = new int[] { 57, 81, 68, 75, 91, 66, 75, 84 };
        System.out.println("原数组内容如下:");

        // 循环遍历原数组
        for (int i = 0; i < scores.length; i++) {
            System.out.print(scores[i] + "\t");
        }

        // 复制原数组的前5个元素到newScores数组中
        int newScores[] = (int[]) Arrays.copyOfRange(scores, 0, 5);
        System.out.println("\n复制的新数组内容如下:");

        // 循环遍历目标数组,即复制后的新数组
        for (int j = 0; j < newScores.length; j++) {
            System.out.print(newScores[j] + "\t");
        }
    }
}

在上述代码中,原数组 scores 中包含有 8 个元素,使用 Arrays.copyOfRange() 方法可以将该数组复制到长度为 5 的 newScores 数组中,截取 scores 数组的前 5 个元素即可。

该程序运行结果如下所示。

原数组内容如下:
57    81    68    75    91    66    75    84   
复制的新数组内容如下:
57    81    68    75    91

使用 arraycopy() 方法

arraycopy() 方法位于 java.lang.System 类中,其语法形式如下:

System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)

其中,srcArray 表示原数组;srcIndex 表示原数组中的起始索引;destArray 表示目标数组;destIndex 表示目标数组中的起始索引;length 表示要复制的数组长度。

使用此方法复制数组时,length+srcIndex 必须小于等于 srcArray.length,同时 length+destIndex 必须小于等于 destArray.length。

注意:目标数组必须已经存在,且不会被重构,相当于替换目标数组中的部分元素。

例假设在 scores 数组中保存了 8 名学生的成绩信息,现在需要复制该数组从第二个元素开始到结尾的所有元素到一个名称为 newScores 的数组中,长度为 12。scores 数组中的元素在 newScores 数组中从第三个元素开始排列。

使用 System.arraycopy() 方法来完成替换数组元素功能的代码如下:

public class Test21 {
    public static void main(String[] args) {
        // 定义原数组,长度为8
        int scores[] = new int[] { 100, 81, 68, 75, 91, 66, 75, 100 };

        // 定义目标数组
        int newScores[] = new int[] { 80, 82, 71, 92, 68, 71, 87, 88, 81, 79, 90, 77 };
        System.out.println("原数组中的内容如下:");

        // 遍历原数组
        for (int i = 0; i < scores.length; i++) {
            System.out.print(scores[i] + "\t");
        }
        System.out.println("\n目标数组中的内容如下:");

        // 遍历目标数组
        for (int j = 0; j < newScores.length; j++) {
            System.out.print(newScores[j] + "\t");
        }
        System.arraycopy(scores, 0, newScores, 2, 8);

        // 复制原数组中的一部分到目标数组中
        System.out.println("\n替换元素后的目标数组内容如下:");

        // 循环遍历替换后的数组
        for (int k = 0; k < newScores.length; k++) {
            System.out.print(newScores[k] + "\t");
        }
    }
}

在该程序中,首先定义了一个包含有 8 个元素的 scores 数组,接着又定义了一个包含有 12 个元素的 newScores 数组,然后使用 for 循环分别遍历这两个数组,输出数组中的元素。最后使用 System.arraycopy() 方法将 newScores 数组中从第三个元素开始往后的 8 个元素替换为 scores 数组中的 8 个元素值。

该程序运行的结果如下所示。

原数组中的内容如下:
100    81    68    75    91    66    75    100   
目标数组中的内容如下:
80    82    71    92    68    71    87    88    81    79    90    77   
替换元素后的目标数组内容如下:
80    82    100    81    68    75    91    66    75    100    90    77   

注意:在使用 arraycopy() 方法时要注意,此方法的命名违背了 Java 的命名惯例。即第二个单词 copy 的首字母没有大写,但按惯例写法应该为 arrayCopy。请读者在使用此方法时注意方法名的书写。

使用 clone() 方法

clone() 方法也可以实现复制数组。该方法是类 Object 中的方法,可以创建一个有单独内存空间的对象。因为数组也是一个 Object 类,因此也可以使用数组对象的 clone() 方法来复制数组。

clone() 方法的返回值是 Object 类型,要使用强制类型转换为适当的类型。其语法形式比较简单:

array_name.clone()

示例语句如下:

int[] targetArray=(int[])sourceArray.clone();

注意:目标数组如果已经存在,将会被重构。

例有一个长度为 8 的 scores 数组,因为程序需要,现在要定义一个名称为 newScores 的数组来容纳 scores 数组中的所有元素,可以使用 clone() 方法来将 scores 数组中的元素全部复制到 newScores 数组中。代码如下:

public class Test22 {
    public static void main(String[] args) {
        // 定义原数组,长度为8
        int scores[] = new int[] { 100, 81, 68, 75, 91, 66, 75, 100 };
        System.out.println("原数组中的内容如下:");

        // 遍历原数组
        for (int i = 0; i < scores.length; i++) {
            System.out.print(scores[i] + "\t");
        }

        // 复制数组,将Object类型强制转换为int[]类型
        int newScores[] = (int[]) scores.clone();
        System.out.println("\n目标数组内容如下:");

        // 循环遍历目标数组
        for (int k = 0; k < newScores.length; k++) {
            System.out.print(newScores[k] + "\t");
        }
    }
}

在该程序中,首先定义了一个长度为 8 的 scores 数组,并循环遍历该数组输出数组中的元素,然后定义了一个名称为 newScores 的新数组,并使用 scores.clone() 方法将 scores 数组中的元素复制给 newScores 数组。最后循环遍历 newScores 数组,输出数组元素。

程序运行结果如下所示。

原数组中的内容如下:
100    81    68    75    91    66    75    100   
目标数组内容如下:
100    81    68    75    91    66    75    100   

从运行的结果可以看出,scores 数组的元素与 newScores 数组的元素是相同的。

注意:以上几种方法都是浅拷贝(浅复制)。浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变

数组排序

Java 语言使用 Arrays 类提供的 sort() 方法来对数组进行排序。

升序

使用 java.util.Arrays 类中的 sort() 方法对数组进行升序分为以下两步:

  1. 导入 java.util.Arrays 包。
  2. 使用 Arrays.sort(数组名) 语法对数组进行排序,排序规则是从小到大,即升序。

假设在数组 scores 中存放了 5 名学生的成绩,现在要实现从低到高排列的功能。在这里使用 Arrays.sort() 方法来实现,具体代码如下:

public static void main(String[] args) {
    // 定义含有5个元素的数组
    double[] scores = new double[] { 78, 45, 85, 97, 87 };
    System.out.println("排序前数组内容如下:");

    // 对scores数组进行循环遍历
    for (int i = 0; i < scores.length; i++) {
        System.out.print(scores[i] + "\t");
    }
    System.out.println("\n排序后的数组内容如下:");

    // 对数组进行排序
    Arrays.sort(scores);

    // 遍历排序后的数组
    for (int j = 0; j < scores.length; j++) {
        System.out.print(scores[j] + "\t");
    }
}

如上述代码所示,要对一个数组进行升序排列,只需要调用 Arrays.sort() 方法即可。运行后的输出结果如下所示。

排序前数组内容如下:
78.0    45.0    85.0    97.0    87.0   
排序后的数组内容如下:
45.0    78.0    85.0    87.0    97.0

降序

在 Java 语言中使用 sort 实现降序有两种方法,简单了解即可。

1)利用 Collections.reverseOrder() 方法(Collections 是一个包装类):

public static void main(String[] args) {
    Integer[] a = { 9, 8, 7, 2, 3, 4, 1, 0, 6, 5 };    // 数组类型为Integer
    Arrays.sort(a, Collections.reverseOrder());
    for (int arr : a) {
        System.out.print(arr + " ");
    }
}

输出结果如下:

9 8 7 6 5 4 3 2 1 0 

2)实现 Comparator 接口的复写 compare() 方法,代码如下:

public class Test {
    public static void main(String[] args) {
        /*
         * 注意,要想改变默认的排列顺序,不能使用基本类型(int,double,char)而要使用它们对应的类
         */
        Integer[] a = { 9, 8, 7, 2, 3, 4, 1, 0, 6, 5 };
        // 定义一个自定义类MyComparator的对象
        Comparator cmp = new MyComparator();
        Arrays.sort(a, cmp);
        for (int arr : a) {
            System.out.print(arr + " ");
        }
    }
}

// 实现Comparator接口
class MyComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        /*
         * 如果o1小于o2,我们就返回正值,如果o1大于o2我们就返回负值, 这样颠倒一下,就可以实现降序排序了,反之即可自定义升序排序了
         */
        return o2 - o1;
    }
}

输出结果如下所示。

9 8 7 6 5 4 3 2 1 0 

注意:使用以上两种方法时,数组必须是包装类型,否则会编译不通过。

在 Java 中实现数组排序的方式很多,除了利用以上的几种方法外,还可以编写自定义方法来实现自己的排序算法。

冒泡排序(非常重要)

冒泡排序(Bubble Sort)是常用的数组排序算法之一,它以简洁的思想与实现方法而备受青睐,也是最先接触的一种排序算法。

冒泡排序的基本思想是:对比相邻的元素值,如果满足条件就交换元素值,把较小的元素值移动到数组前面,把大的元素值移动到数组后面(也就是交换两个元素的位置),这样数组元素就像气泡一样从底部上升到顶部

冒泡排序的算法比较简单,排序的结果稳定,但时间效率不太高。Java 中的冒泡排序在双层循环中实现,其中外层循环控制排序轮数,总循环次数为要排序数组的长度减 1。而内层循环主要用于对比相邻元素的大小,以确定是否交换位置,对比和交换次数依排序轮数而减少。

例获取用户在控制台输入的 5 个成绩信息,将这些成绩保存到数组中,然后对数组应用冒泡排序,并输出排序后的结果,实现步骤如下。

(1) 创建一个 Test24 类文件,在 main() 方法中开始编码。首先创建 Scanner 类的实例后声明 double 类型的 score 数组,然后接收用户在控制台输入的成绩,并保存到元素中。代码如下:

public static void main(String[] args) {    
    Scanner scan = new Scanner(System.in);    //Scanner类用来获取用户输入
    double[] score = new double[5];    
    for (int i = 0; i < score.length; i++) {        
        System.out.print("请输入第 " + (i + 1) + " 个成绩:");        
        score[i] = scan.nextDouble();    
    }
}

(2) 在对 score 数组排序之前,首先输出数组中各个元素的值。代码如下:

System.out.println("排序前的元素值:");
for(double val:score) {    
    System.out.print(val+"\t");
}
System.out.println();

(3) 通过冒泡排序方法实现对 score 数组的排序,在实现时需要借助一个临时变量。代码如下:

public static void main(String[] args) {
    System.out.println("通过冒泡排序方法对数组进行排序:");
    for (int i = 0; i < score.length - 1; i++) {
        // 比较相邻两个元素,较大的数往后冒泡
        for (int j = 0; j < score.length - 1 - i; j++) {
            if (score[j] > score[j + 1]) {
                double temp = score[j + 1]; // 把第一个元素值保存到临时变量中
                score[j + 1] = score[j]; // 把第二个元素值转移到第一个元素变量中
                score[j] = temp; // 把临时变量(第一个元素的原值)保存到第二个元素中
            }
            System.out.print(score[j] + " "); // 对排序后的数组元素进行输出
        }
        System.out.print("【");
        for (int j = score.length - 1 - i; j < score.length; j++) {
            System.out.print(score[j] + " ");
        }
        System.out.println("】");
    }
}

(4) 运行前面的代码进行测试,如下所示。

请输入第 1 个成绩:77
请输入第 2 个成绩:90
请输入第 3 个成绩:68
请输入第 4 个成绩:59
请输入第 5 个成绩:80
排序前的元素值:
77.0    90.0    68.0    59.0    80.0   
通过冒泡排序方法对数组进行排序:
77.0 68.0 59.0 80.0 【90.0 】
68.0 59.0 77.0 【80.0 90.0 】
59.0 68.0 【77.0 80.0 90.0 】
59.0 【68.0 77.0 80.0 90.0 】

快速排序法

快速排序(Quicksort)是对冒泡排序的一种改进,是一种排序执行效率很高的排序算法

快速排序的基本思想是:通过一趟排序,将要排序的数据分隔成独立的两部分,其中一部分的所有数据比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此使整个数据变成有序序列。

具体做法是:假设要对某个数组进行排序,

  1. 首先需要任意选取一个数据(通常选用第一个数据)作为关键数据,
  2. 然后将所有比它小的数都放到它的前面,所有比它大的数都放到它的后面。
  3. 这个过程称为一趟快速排序;递归调用此过程,即可实现数据的快速排序。

例利用快速排序法对一数组进行排序,实现步骤如下。

(1) 声明静态的 getMiddle() 方法,该方法需要返回一个 int 类型的参数值,在该方法中传入 3 个参数。代码如下:

public static int getMiddle(int[] list, int low, int high) {
    int tmp = list[low]; // 数组的第一个值作为中轴(分界点或关键数据)
    while (low < high) {
        while (low < high && list[high] > tmp) {
            high--;
        }
        list[low] = list[high]; // 比中轴小的记录移到低端
        while (low < high && list[low] < tmp) {
            low++;
        }
        list[high] = list[low]; // 比中轴大的记录移到高端
    }
    list[low] = tmp; // 中轴记录到尾
    return low; // 返回中轴的位置
}

(2) 创建静态的 unckSort() 方法,在该方法中判断 low 参数是否小于 high 参数,如果是则调用 getMiddle() 方法,将数组一分为二,并且调用自身的方法进行递归排序。代码如下:

public static void unckSort(int[] list,int low,int high) {
    if(low < high) {
        int middle = getMiddle(list,low,high);    // 将list数组一分为二
        unckSort(list,low,middle-1);    // 对低字表进行递归排序
        unckSort(list,middle+1,high);    // 对高字表进行递归排序
    }
}

(3) 声明静态的 quick() 方法,在该方法中判断传入的数组是否为空,如果不为空,则调用 unckSort() 方法进行排序。代码如下:

public static void quick(int[] str) {
    if(str.length > 0) {
        // 查看数组是否为空
        unckSort(str,0,str.length-1);
    }
}

(4) 在 main() 方法中声明 int 类型的 number 数组,接着输出该数组中的元素。然后调用自定义的 quick() 方法进行排序,排序后重新输出数组中的元素。代码如下:

int[] number={13,15,24,99,14,11,1,2,3};
System.out.println("排序前:");
for(int val:number) {
    System.out.print(val+" ");
}
quick(number);
System.out.println("\n排序后:");
for(int val:number) {
    System.out.print(val +" ");
}

运行前面的代码进行测试,输出结果如下:

排序前:
13 15 24 99 14 11 1 2 3
排序后:
1 2 3 11 13 14 15 24 99 

选择排序法

选择排序是指每一趟从待排序的数据元素中选出最大(或最小)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。例如,使用选择排序法重新对 number 数组中的元素进行排序。

int[] number = {13,15,24,99,4,1};
String end = "\n";
int index;
for (int i = 1;i < number.length;i++) {
    index = 0;
    for(int j = 1;j <= number.length-i;j++) {
        if (number[j] > number[index]) {
            index = j;    // 查找最大值
        }
    }
    end = number[index] + " " + end;    // 定位已排好的数组元素
    int temp = number[number.length-i];
    number[number.length-1] = number[index];
    number[index] = temp;
    System.out.print("【");
    for (int j = 0;j < number.length-i;j++) {
        System.out.print(number[j]+" ");
    }
    System.out.print("】"+end);
}

执行上述代码,查看每一趟排序后的结果,运行结果如下所示。

【13 15 24 1 4 】99
【13 15 4 1 】24 99
【13 1 4 】15 24 99
【4 1 】13 15 24 99
【1 】4 13 15 24 99 

直接插入排序法

直接插入排序的基本思想是:将 n 个有序数存放在数组 a 中,要插入的数为 x,

  1. 首先确定 x 插在数组中的位置 p,
  2. 然后将 p 之后的元素都向后移一个位置,空出 a(p),将 x 放入 a(p),
  3. 这样可实现插入 x 后仍然有序。

通过直接插入的方法对上述例子中的 number 数组进行排序。创建一个 Test27 类文件,在 main() 方法中开始编码,具体代码如下:

public static void main(String[] args) {
    int[] number = { 13, 15, 24, 99, 4, 1 };
    System.out.println("排序前:");
    for (int val : number) { // 遍历数组元素
        System.out.print(val + " "); // 输出数组元素
    }
    int temp, j;
    for (int i = 1; i < number.length; i++) {
        temp = number[i];
        for (j = i - 1; j >= 0 && number[j] > temp; j--) {
            number[j + 1] = number[j];
        }
        number[j + 1] = temp;
    }
    System.out.println("\n排序后:");
    for (int val : number) { // 遍历数组元素
        System.out.print(val + " "); // 输出数组元素
    }
}

在上述代码中,首先在控制台输出 number 数组中的元素,然后通过 for 循环对数组中的元素进行排序,最后再次输出排序后的元素。

执行上述代码,最终的输出结果如下:

排序前:
13 15 24 99 4 1
排序后:
1 4 13 15 24 99 

Java数组的总结

数组(Array)是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来唯一确定数组中的元素。根据数组的维度,可以将其分为一维数组、二维数组和多维数组等。

一维数组

数组中每个元素都只带有一个下标,是数组中最简单的一种数组。

1. 声明

声明一维数组语法有两种格式(推荐使用第一种)。

type[] arrayName;  // 数据类型[] 数组名;

或者

type arrayName[];  // 数据类型 数组名[];

2. 分配空间

分配空间语法格式如下。

arrayName = new type[size];  // 数组名 = new 数据类型[数组长度];

3. 初始化

Java 中初始化数组有以下 3 种方式。

  • 使用 new 指定数组大小后进行初始化
  • 使用 new 指定数组元素的值
  • 直接指定数组元素的值
// 使用 new 指定数组大小后进行初始化
int[] number = new int[5];
number[0] = 1;
number[1] = 2;
number[2] = 3;
number[3] = 5;
number[4] = 8;

// 使用 new 指定数组元素的值(2种方式)
int[] number = new int[] { 1, 2, 3, 5, 8 };
int[] number = new int[5] { 1, 2, 3, 5, 8 };

// 直接指定数组元素的值(2种方式)
int[] number = { 1, 2, 3, 5, 8 };

int[] number;
number={1,2,3,5,8};

4. 使用

  • 获取单个元素
  • 获取全部元素
// 获取单个元素
int[] number = { 1, 2, 3, 5, 8 };
System.out.println("获取第一个元素:" + number[0]);

// 使用for语句获取全部元素
int[] number = { 1, 2, 3, 5, 8 };
for (int i = 0; i < number.length; i++) {
    System.out.println("第" + (i + 1) + "个元素的值是:" + number[i]);
}
// 使用foreach语句获取全部元素
for (int val : number) {
    System.out.print("元素的值依次是:" + val + "\t");
}

二维数组

二维数组被看作数组的数组

1. 声明

声明二维数组语法有两种格式(推荐使用第一种)。

type[][] arrayName;  // 数据类型[][] 数组名;

或者

type arrayName[][];  // 数据类型 数组名[][];

第一个中括号表示行,第二个中括号表示列。

2. 初始化

二维数组可以通过以下 3 种方式来指定元素的初始值。

type[][] arrayName = new type[][]{值 1,值 2,值 3,…,值 n};  // 在定义时初始化
type[][] arrayName = new type[size1][size2];  // 给定空间,在赋值
type[][] arrayName = new type[size][];  // 数组第二维长度为空,可变化

// 定义时初始化i
nt[][] temp = new int[][]{{1,2},{3,4}};
// 给定空间在赋值
int[][] temp = new int[2][2];
// 数组第二维长度为空,可变化
int[][] temp = new int[2][];

3. 使用

  • 获取单个元素:arrayName[i-1][j-1];arrayName 表示数组名称,i 表示数组的行数,j 表示数组的列数。
  • 获取全部元素:使用嵌套 for 循环或 for each 循环语句。
  • 获取整行元素:需要将行数固定,然后只遍历该行中的全部列即可。
  • 获取整列元素:获取指定列的元素与获取指定行的元素相似,保持列不变,遍历每一行的该列即可。

数组操作

1. 比较数组 equals

比较数组元素的个数和对应位置的元素是否相等。

Arrays.equals(arrayA, arrayB);

arrayA 是用于比较的第一个数组,arrayB 是用于比较的第二个数组。

2. 填充数组 fill

在指定位置进行数值填充。

Arrays.fill(array,value);

array 表示数组,value 表示填充的值。只能使用同一个数值进行填充。

3. 数组查找 binarySearch

从数组中查询指定位置的元素,或者查询某元素在指定数组中的位置,语法格式如下。

binarySearch(Object[] a,Object key);

a 表示要搜索的数组,key 表示要搜索的值。

在数组中指定范围内查找,语法格式如下。

binarySearch(Object[] a,int fromIndex,int toIndex,Object key);

a 表示要进行查找的数组,fromIndex 指定范围的开始处索引(包含开始处),toIndex 指定范围的结束处索引(不包含结束处),key 表示要搜索的元素。

4. 复制数组

1) copyOf()

Arrays.copyOf(dataType[] srcArray,int length);

srcArray 表示要进行复制的数组,length 表示复制后的新数组的长度

2) CopyOfRange()

Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex)

srcArray 表示原数组,startIndex 表示开始复制的起始索引,endIndex 表示终止索引。

3) arraycopy()

System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)

srcArray 表示原数组,srcIndex 表示源数组中的起始索引,destArray 表示目标数组,destIndex 表示目标数组中的起始索引,length 表示要复制的数组长度。

4) clone()

array_name.clone()

数组排序

Java 数组中有 5 种常见排序方法,分别是:

  1. Arrays.sort()
  2. 冒泡排序
  3. 快速排序
  4. 选择排序
  5. 直接插入

常见问题解答

1. 声明数组需要注意什么?

声明数组时,一定要考虑数组的最大容量,防止容量不够的现象。数组一旦被声明,它的容量就固定了,不容改变。如果想在运行程序时改变容量,就需要用到集合。关于集合我们会在教程后面讲解。

2. 数组在平时的程序代码中使用是否频繁?

数组有一个缺点,就是一旦声明,就不能改变容量,这个也是其使用频率不高的原因。一般存储数据会使用集合或 Vector 来存储数据,后面我们会讲到。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342