Java数组(Array)最全汇总(中篇)
作者:小熊coder
前言
本章是关于Java数组的最全汇总,本篇为汇总中篇,主要讲了二维数组和不规则的数组的相关内容。
数组是最常见的一种数据结构,它是相同类型的用一个标识符封装到一起的基本类型数据序列或者对象序列。
数组使用一个统一的数组名和不同的下标来唯一确定数组中的元素。
实质上,数组是一个简单的线性序列,因此访问速度很快。
本章将详细介绍 Java 中数组的创建、初始化和处理方法,如获取数组长度、查找数组元素和数组排序等。
本章学习要点
- 掌握一维数组的创建方法
- 掌握一维数组的初始化方法
- 熟练掌握一维数组的应用
- 掌握二维数组的创建
- 掌握二维数组的初始化
- 熟练掌握二维数组元素的访问方式
- 掌握数组的复制方法
- 掌握搜索数组元素的方法
- 掌握对数组的排序算法
Java二维数组详解
为了方便组织各种信息,计算机常将信息以表的形式进行组织,然后再以行和列的形式呈现出来。
二维数组的结构决定了其能非常方便地表示计算机中的表,以第一个下标表示元素所在的行,第二个下标表示元素所在的列。
下面简单了解一下二维数组,包括数组的声明和初始化。
创建二维数组
在 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][]; // 数组第二维长度为空,可变化
例 1
使用第一种方式声明 int 类型的二维数组,然后初始化该二维数组。代码如下:
int[][] temp = new int[][]{{1,2},{3,4}};
上述代码创建了一个二行二列的二维数组 temp,并对数组中的元素进行了初始化。图 1 所示为该数组的内存结构。
图1 二维数组内存结构
使用第二种方式声明 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。
例 2
通过下标获取 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 循环)。
例 3
使用 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
例 4
假设有一个矩阵为 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) { ...... } }
把【例2】修改为使用 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]]
获取整行元素
除了获取单个元素和全部元素之外,还可以单独获取二维数组的某一行中所有元素的值,或者二维数组中某一列元素的值。
获取指定行的元素时,需要将行数固定,然后只遍历该行中的全部列即可。
例 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
获取整列元素
获取指定列的元素与获取指定行的元素相似,保持列不变,遍历每一行的该列即可。
例 6
编写一个案例,接收用户在控制台中输入的列数,然后获取二维数组中所有行中该列的值。代码如下:
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不规则数组
通过前面的学习我们知道 Java 实际上没有多维数组,只有一维数组。
多维数组被解释为是数组的数组,所以因此会衍生出一种不规则数组。
规则的 4×3 二维数组有 12 个元素,而不规则数组就不一定了。
如下代码静态初始化了一个不规则数组。
int intArray[][] = {{1,2}, {11}, {21,22,23}, {31,32,33}};
高维数组(二维以及二维以上的数组称为高维数组)是 4 个元素,但是低维数组元素个数不同,如图 1 所示,其中第 1 个数组有两个元素,第 2 个数组有 1 个元素,第 3 个数组有 3 个元素,第 4 个数组有 3 个元素。这就是不规则数组。
图 1 不规则数组
动态初始化不规则数组比较麻烦,不能使用 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 个元素,它们的下标索引如图 2 所示,可见其中下标 [0][2]、[1][1] 和 [1][2] 是不存在的,如果试图访问它们则会抛出下标越界异常。
图 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] 不存在所以会发生下标越界异常。
Java数组也是一种数据类型
Java 的数组要求所有的数组元素具有相同的数据类型。
因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。
因为 Java 语言是面向对象的语言,而类与类之间可以支持继承关系(从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为),这样可能产生一个数组里可以存放多种数据类型的假象。
例如有一个水果数组,要求每个数组元素都是水果,实际上数组元素既可以是苹果,也可以是香蕉(苹果、香蕉都继承了水果,都是一种特殊的水果),但这个数组的数组元素的类型还是唯一的,只能是水果类型。
一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。
即使把某个数组元素的数据清空,但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。
Java 的数组既可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。
值得指出的是,数组也是一种数据类型,它本身是一种引用类型。
例如 int 是一个基本类型,但 int[](这是定义数组的一种方式)就是一种引用类型了。
int[] 是一种类型吗?怎么使用这种类型呢?
没错,int[] 就是一种数据类型,与 int 类型、String 类型相似,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等。
使用 int[] 类型来定义变量、进行类型转换时与使用其他普通类型没有任何区别。
int[] 类型是一种引用类型,创建 int[] 类型的对象也就是创建数组,需要使用创建数组的语法。
Java中到底有没有多维数组(长篇神文)?
Java 中没有多维数组的概念,从数组底层的运行机制上来看 Java 没有多维数组,但是 Java 提供了支持多维数组的语法,可以实现多维数组的功能。
Java 语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。
定义数组类型的语法
type[] arrName;
是典型的一维数组的定义语法,其中 type 是数组元素的类型。
如果希望数组元素也是一个引用,而且是指向 int 数组的引用,则可以把 type 具体成 int[](前面已经指出,int[] 就是一种类型,int[] 类型的用法与普通类型并无任何区别),那么上面定义数组的语法就是
int[][] arrName
如果把 int 这个类型扩大到 Java 的所有类型(不包括数组类型),则出现了定义二维数组的语法:
type[][] arrName;
Java 语言采用上面的语法格式来定义二维数组,但它的实质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组。
接着对这个“二维数组”执行初始化,同样可以把这个数组当成一维数组来初始化,把这个“二维数组”当成一个一维数组,其元素的类型是 type[] 类型,则可以采用如下语法进行初始化:
arrName = new type[length][]
上面的初始化语法相当于初始化了一个一维数组,这一维数组的长度是 length。
同样,因为这个一维数组的数组元素是引用类型(数组类型)的,所以系统为每个数组元素都分配初始值:null。
这个二维数组实际上完全可以当成一维数组使用:使用
new type[length]
初始化一维数组后,相当于定义了 length 个 type 类型的变量。
类似的,使用
new type[length][]
初始化这个数组后,相当于定义了 length 个 type[] 类型的变量。
当然,这些 type[] 类型的变量都是数组类型,因此必须再次初始化这些数组。
下面程序示范了如何把二维数组当成一维数组处理。
public class TwoDimensionTest { public static void main(String[] args) { // 定义一个二维数组 int[][] a; // 把a当成一维数组进行初始化,初始化a是一个长度为4的数组 // a数组的数组元素又是引用类型 a = new int[4][]; // 把a数组当成一维数组,遍历a数组的每个数组元素 for (int i = 0, len = a.length; i < len; i++) { System.out.println(a[i]); // 输出 null null null null } // 初始化a数组的第一个元素 a[0] = new int[2]; // 访问a数组的第一个元素所指数组的第二个元素 a[0][1] = 6; // a数组的第一个元素是一个一维数组,遍历这个一维数组 for (int i = 0, len = a[0].length; i < len; i++) { System.out.println(a[0][i]); // 输出 0 6 } } }
上面程序中粗体字代码部分把 a 这个二维数组当成一维数组处理,只是每个数组元素都是 null,所以看到输出结果都是 null。
下面结合示意图来说明这个程序的执行过程。
程序中代码
int[][] a;
将在栈内存中定义一个引用变量,这个变量并未指向任何有效的内存空间,此时的堆内存中还未为这行代码分配任何存储区。
程序中代码
a = new int[4][];
对a 数组执行初始化,这行代码让 a 变量指向一块长度为 4 的数组内存,这个长度为 4 的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认的初始值:null。此时 a 数组在内存中的存储示意图如图 1 所示。
图 1 将二维数组当成一维数组初始化的存储示意图
从图 1 来看,虽然声明 a 是一个二维数组,但这里丝毫看不出它是一个二维数组的样子,完全是一维数组的样子。
这个一维数组的长度是 4,只是这 4 个数组元素都是引用类型,它们的默认值是 null。
所以程序中可以把 a 数组当成一维数组处理,依次遍历 a 数组的每个元素,将看到每个数组元素的值都是 null。
由于 a 数组的元素必须是 int[] 数组,所以接下来的程序对 a[0] 元素执行初始化,也就是让图 1 右边堆内存中的第一个数组元素指向一个有效的数组内存,指向一个长度为 2 的 int 数组。
因为程序采用动态初始化 a[0] 数组,因此系统将为 a[0] 所引用数组的每个元素分配默认的初始值:0,然后程序显式为 a[0] 数组的第二个元素赋值为 6。
此时在内存中的存储示意图如图 2 所示。
图 2 初始化a[0]后的存储示意图
图 2 中灰色覆盖的数组元素就是程序显式指定的数组元素值。
TwoDimensionTest.java 接着迭代输出 a[0] 数组的每个数组元素,将看到输出 0 和 6。
是否可以让图 2 中灰色覆盖的数组元素再次指向另一个数组?这样不就可以扩展成三维数组,甚至扩展成更多维的数组嘛?
不能!至少在这个程序中不能。
因为 Java 是强类型语言,当定义 a 数组时,已经确定了 a 数组的数组元素是 int[] 类型,则 a[0] 数组的数组元素只能是 int 类型,所以灰色覆盖的数组元素只能存储 int 类型的变量。
对于其他弱类型语言,例如 JavaScript 和 Ruby 等,确实可以把一维数组无限扩展,扩展成二维数组、三维数组…,如果想在 Java 语言中实现这种可无限扩展的数组,则可以定义一个 Object[] 类型的数组,这个数组的元素是 Object 类型,因此可以再次指向一个 Object[] 类型的数组,这样就可以从一维数组扩展到二维数组、三维数组…
从上面程序中可以看出,初始化多维数组时,可以只指定最左边维的大小;当然,也可以一次指定每一维的大小。例如下面代码:
// 同时初始化二维数组的两个维数 int[][] b = new int[3][4];
上面代码将定义一个 b 数组变量,这个数组变量指向一个长度为 3 的数组,这个数组的每个数组元素又是一个数组类型,它们各指向对应的长度为 4 的 int[] 数组,每个数组元素的值为 0。
这行代码执行后在内存中的存储示意图如图 3 所示。
图 3 同时初始化二维数组的两个维数后的存储示意图
还可以使用静态初始化方式来初始化二维数组。
使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值。如下代码所示:
// 使用静态初始化语法来初始化一个二维数组 String[][] str1 = new String[][]{new String[3], new String[]{“hello”}}; // 使用简化的静态初始化语法来初始化二维数组 String[][] str2 = {new String[3], new String [] {“hello”}};
上面代码执行后内存中的存储示意图如图 4 所示。
图 4 采用静态初始化语法初始化二维数组的存储示意图
通过上面讲解可以得到一个结论:二维数组是一维数组,其数组元素是一维数组。三维数组也是一维数组,其数组元素是二维数组…… 从这个角度来看,Java 语言里没有多维数组。
到此这篇关于Java数组(Array)最全汇总(中篇)的文章就介绍到这了,其他两个部分的内容(上、下篇)请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- Java数组(Array)最全汇总(上篇)
- Java之数组在指定位置插入元素实现
- Java自定义一个变长数组的思路与代码
- Java中如何将 int[] 数组转换为 ArrayList(list)
- Java中将 int[] 数组 转换为 List分享
- java如何将int数组转化为Integer数组
- 浅谈Java当作数组的几个应用场景
- 计算Java数组长度函数的方法以及代码分析
- Java C++题解leetcode915分割数组示例
- Java 从json提取数组并转换为list的操作方法
- Java数据结构之稀疏数组的实现与应用
- Java C++题解leetcode1441用栈操作构建数组示例
- Java postgresql数组字段类型处理方法详解
- Java中数组的常见操作合集
- 关于Java SE数组的深入理解
- Java二维数组与稀疏数组相互转换实现详解
- Java数组队列及环形数组队列超详细讲解
- Java数组(Array)最全汇总(下篇)