掘金 后端 ( ) • 2024-03-22 13:26

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

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

一维数组

格式 1(推荐采用):元素类型[] 数组名;

格式 2:元素类型 数组名[];

数组元素的类型可以是基本类型,也可以是类或接口。

数组初始化

声明完成就要对数组进行初始化,数组初始化的过程就是为数组每一个元素分配内存空间。一旦初始化之后数组的长度就确定下来不能再变化了。

数组初始化可以分为静态初始化和动态初始化。

静态初始化

静态初始化就是将数组的元素放到大括号中,元素之间用逗号(,)分隔。

使用场景: 在已知数组的每一个元素内容情况下使用。

示例代码如下:

建议此种完整的形式

// 完整格式
String[] nameArray = new String[] {"张飞", "店小二", "老王"};
// 简写形式
String[] nameArray = {"张飞", "店小二", "老王"};

注意:如果在返回的时候使用简写形式,会编译不通过, return {"张飞", "店小二", "老王"};。这种情况下只能写成 return new String[] {"张飞", "店小二", "老王"};

动态初始化

动态初始化使用 new 运算符分配指定长度,系统会自动为其分配默认值。语法为:new 元素数据类型[数组长度] ;。默认值的规则和类的成员变量的规则保持一致, 这里还没有介绍 new 关键字和如何新建类,所以详细说明下。

动态初始化后数据类型默认值

数组长度虽然可以动态确定,同样的重要在确定后就不可以变。

还有一个小细节,不能在给定初始值的同时给定长度,例如以下这种写法是是不允许的:

String[] nameArray = new String[3] {"张三", "李四", "王五"};

长度要求是非负数。所以长度至少为 0。而长度为 0 的数组称为空数组,在实际生产中也有其特殊用途。

多维数组

一般可用于数学计算,比如二维数组的矩阵运算等场景。

多维数组一般用得不多,超过 3 层后可读性很差,很难维护了。 下面主要讲介绍二维数组。

二维数组声明

当数组中每个元素又可以带有多个下标时,这种数组就是“多维数组”。Java 中声明二维数组需要有两个中括号,具体有三种语法如下:

元素数据类型[][] 数组变量名;
元素数据类型 数组变量名[][];
元素数据类型[] 数组变量名[];

二维数组的初始化

二维数组的初始化也分为静态初始化和动态初始化。

静态初始化

int[][] intArray = new int[][] {
  { 1, 2, 3 }, 
  { 11, 12, 13 }, 
  { 21, 22, 23 }
};

提示 严格意义上说 Java 中并不存在真正意义上的多维数组,可认为依旧是一维数组,不过数组中的元素也是数组,以此类推三维数组就是数组的数组的数组了,例如 { { {1, 2}, {3} }, { {21}, {22, 23} } } 表示一个三维数组。

动态初始化

语法: new 元素数据类型[高维数组长度][低维数组长度];

不规则数组

由于 Java 多维数组是数组的数组,因此会衍生出一种不规则数组。

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

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

数组的访问

通过下标访问

数组有一个 length 属性,且只读。数组的长度,至少为 0。

通过下标进行访问。例如 arr[0] 表示第 1 个元素, arr[1] 表示第 2 个元素,依此类推直到 arr[length - 1]。

我对 length 的理解:由于 length 的类型为 int 类型,理论上可以表示的最大长度是 int 类型的最大值。但是这样做在运行行可能会抛出 OOM 异常。实际上一般也用不到长度这么长的数组。

// 静态初始化
int arr[] = new int[3];
// 设置值
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
// 直接输出将打印内存地址
System.out.println(arr);
// 输出数组的长度
System.out.println(arr.length);
// 输出数组的第 0 个元素
System.out.println(arr[0]);
System.out.println(arr[1]);
// 输出数组的第 2 个元素
System.out.println(arr[2]);
// 输出数组的第 3 个元素 将产生数组越界异常
System.out.println(arr[3]);

数组遍历

普通遍历

for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

使用 foreach 遍历

foreach 关键字。这是 jdk 1.5 新增的关键字。iterate over an array or Iterable。可用迭代数组和 Iterable 对象,比如一些常见的集合类型。使用 foreach 的好处是它隐藏了下标, 数组遍历强烈建议使用。除非是需要下标值。

for (类型 变量: 数组) {
    // do something
}

理解数组的内存结构

数组类型和基本类型是有明显不同的,一个基本类型变量,内存中只会有一块对应的内存空间。但数组有两块:一块用于存储数组内容本身(栈内存),另一块用于存储内容的位置(堆内存)。

数组的内存结构

数组中常见的异常(Exception): 访问到不存在的角标: ArrayIndexOutOfBoundsException

加餐

命令行数组

Java 的 main 方法参数 String[] args 其实也是一个字符串数组,这里可以通过 java Demo hello world 往数组传至。

以下 eclipse 添加参数的截图。

工具类 Arrays 的使用

提供了数组拷贝由 java.lang.System 的静态方法

public static native void arraycopy(Object src, int  srcPos, Object dest, int destPos, int length)

// 数组打印
Arrays.toString(array);
Arrays.deepToString

// 升序排列
Arrays.sort(array);

// 数组的赋值
Arrays.copyOf
Arrays.copyOfRange

// 数组元素的折半查找
binarySearch(Object[] a, Object key)

小案例

int[] array = { 1, 5, 3 };
int[] bArray = { 11, 55, 33 };

// 数组拷贝
System.arraycopy(array, 1, bArray, 1, 1);
System.out.println(Arrays.toString(bArray));

// 数组打印
System.out.println(Arrays.toString(array));

// 升序排列
Arrays.sort(array);
System.out.println(Arrays.toString(array));

三方数组增强工具

Apache Commons Lang 库提供了对数组的增强操作类 ArrayUtils。

下面这个例子实现了数组的翻转。

ArrayUtils.reverse(array);

参考