cosmos 项目 Java 语言专题:深入理解二维 ArrayList(2D Array List)的声明、常用操作与适用场景
2026/9/23 12:01:43 网站建设 项目流程

cosmos 项目 Java 语言专题:深入理解二维 ArrayList(2D Array List)的声明、常用操作与适用场景

【免费下载链接】cosmosWorld's largest Contributor driven code dataset | Used in Quark Search Engine, @OpenGenus IQ, OpenGenus Visual Project项目地址: https://gitcode.com/gh_mirrors/co/cosmos

二维 ArrayList 是 Java 集合框架中“动态二维结构”的典型实现,它以ArrayList<ArrayList<T>>的形式用多个一维列表拼装成行-列模型,无需像原生数组那样预先固定大小,元素持续添加时容量会自动增长。在 cosmos 仓库的 Java 语言目录中,Readme.md 系统介绍了这一数据结构,并配套提供了可直接运行的示例源码。读完本文,你将掌握 2D ArrayList 的声明方式、逐行初始化技巧、核心 API 的调用细节,以及它与原生 2D 数组(int[][])在内存模型与适用场景上的本质差异。

一、从动态数组到二维 ArrayList:核心概念

ArrayList是数组(Array)的动态版本。原生数组在声明时必须预定义长度,而 ArrayList 内部维护一个可扩容的底层数组,当元素数量超过当前容量时会自动增长,因此使用者在多数场景下无需关心容量上限。

将这一思想推广到二维,就得到2D ArrayList:它本质上是“存放列表的列表”,每个外层元素都是一个独立的一维ArrayList。原文档给出的最简定义如下:

ArrayList<ArrayList<Integer> > arrLL = new ArrayList<ArrayList<Integer> >();

从类型结构看,arrLL的每一个元素都是ArrayList<Integer>,因此可以把它理解为“由多个动态行组成的动态二维表”。相比原生int[][],它的行本身也是动态的——每一行既可以有不同长度,也可以在运行时按需添加、删除或整体移除。

按照原文档的划分,ArrayList 家族主要有两类常见变体:

  • 1-D Array List(一维 ArrayList,即通常所说的 ArrayList);
  • Multidimensional Array List(多维 ArrayList)。

其中2-D Array List是最简单、最常用的多维形态,也是本文的讲解对象。仓库中与之形成对照的另一份文档 readme-2DArray.md 则介绍原生 2D 数组:

int[][] arr = new int[10][20];

两条声明放在一起,恰好展示了“静态二维”与“动态二维”两种建模思路的区别。

二、声明与初始化:如何正确“开辟”二维 ArrayList

声明ArrayList<ArrayList<Integer>>只创建了一个空的“容器”,此时它内部一个行都还没有。想要真正使用二维结构,必须逐行为外层列表添加一维 ArrayList 实例,否则对arrLL.get(i)的任何访问都会抛出IndexOutOfBoundsException

仓库示例 2d-array-list-java.java 演示了标准的初始化流程:

// 创建 2D ArrayList ArrayList<ArrayList<Integer> > arrLL = new ArrayList<ArrayList<Integer> >(); // 分配第 0 行:通过 'new' 关键字创建内层列表,默认值为空 arrLL.add(new ArrayList<Integer>()); // 在第 0 行索引 0 处写入 13(覆盖默认状态) arrLL.get(0).add(0, 13);

这里有两个关键点值得注意:

  1. 必须先 add 行,再对行内 add 元素arrLL.add(new ArrayList<Integer>())负责向“行集合”中添加一个空行;随后arrLL.get(0)取出该行引用,再调用其add(0, 13)在指定下标插入元素。
  2. 每行相互独立。外层列表只保存内层列表的引用,任何一行的长度变化(增删元素)都不会影响其他行,这正是“二维”中两个维度都具备动态性的来源。

如果需要构造一个“n 行 × m 列”的规则二维结构,可在初始化阶段用循环为每一行创建列表并填充占位值,例如:

ArrayList<ArrayList<Integer>> grid = new ArrayList<>(); int rows = 3, cols = 4; for (int i = 0; i < rows; i++) { grid.add(new ArrayList<>()); for (int j = 0; j < cols; j++) { grid.get(i).add(0); // 以 0 作为默认值占位 } }

这种“先铺行、再填格”的模式是 2D ArrayList 最常见的构造方式,也是与原生int[][]声明后即可直接赋值(默认填充 0)之间最直观的差异。

三、基于仓库源码的完整实操:常用 API 逐项解析

2d-array-list-java.java 不仅是初始化演示,还依次展示了 2D ArrayList 在“二维视角”下最常用的方法。下面结合源码逐一解析。

3.1 整体输出:toString 直接打印二维结构

System.out.println("2D ArrayList :"); System.out.println(arrLL);

ArrayList重写了toString(),嵌套列表会输出类似[[13]]的嵌套括号形式,因此二维结构可以一次性打印,无需手写双重循环。这是 ArrayList 相对原生数组(打印数组名只会得到[I@1b6d3586这类内存标识)在调试体验上的明显优势。

3.2 行内查找:indexOf 与 lastIndexOf

int ans = arrLL.get(0).indexOf(13); System.out.println(" Index of element 13 is "+ ans); int ans2 = arrLL.get(0).lastIndexOf(13); System.out.println(" Last Index of element 13 is "+ ans2);
  • arrLL.get(0)取得第 0 行这个一维列表;
  • indexOf(13)返回该行中 13 首次出现的下标(不存在时返回 -1);
  • lastIndexOf(13)返回该行中 13 最后一次出现的下标。

这两个方法把二维查询拆解为“先定位行,再在行内线性扫描”,时间复杂度为 O(行内元素数)。

3.3 整行删除:remove 按行索引移除

arrLL.remove(0); System.out.println(arrLL);

remove(0)删除的是整行(即外层列表的第 0 个元素),随后原来的第 1 行自动前移补位。这是 2D ArrayList 相对原生数组最灵活的地方——原生数组无法在运行时收缩维度,而 2D ArrayList 可以随时增删整行。

3.4 元素存在性检查:contains

源码末尾通过 contains 判断某元素是否存在于指定行:

System.out.println(x.get(0).contains(13)); System.out.println(x.get(0).contains(1));

需要提醒的是:从源码结构看,此处使用的变量x在文件内并未声明(前文一直使用arrLL),且第 0 行刚被remove(0)删除,因此这两行若直接编译运行会因变量未定义而失败。这也反过来提醒读者两条实战要点:

  • 行删除后,原有的行下标会整体前移,继续用旧下标访问可能得到错误的行;
  • 引用行集合的变量应保持命名一致,避免“抄写时引入未声明变量”这类低级错误。若将x替换为arrLL并调整行下标,contains(13)的返回结果即为该行是否包含 13 的布尔判断。

3.5 其他高频 API 一览

除上述方法外,2D ArrayList 还继承自AbstractList的常用能力:

方法作用在二维场景中的语义
add(ArrayList<T> row)向末尾追加元素新增一行
add(int index, ArrayList<T> row)在指定下标插入元素在指定行位置插入一行
get(int row)按下标取元素取出一整行(ArrayList<T>
set(int row, ArrayList<T> newRow)替换指定下标元素整行替换
size()返回元素个数返回行数
isEmpty()是否为空是否无行
clear()清空所有元素删除全部行
remove(int index)删除指定下标元素删除整行

结合内层列表,还能实现“按坐标读写单个格子”:arrLL.get(row).get(col)读取第 row 行第 col 列的值,arrLL.get(row).set(col, value)写入该位置——这一组合本质上等价于二维数组的下标访问arr[row][col]

四、与原生 2D 数组的对比:内存模型与取舍

仓库同时收录了原生 2D 数组的实现 2d-array.java 与配套说明 readme-2DArray.md。该示例从用户输入读取行数、列数与矩阵元素,再按行列打印:

int[][] arr = new int[rows][columns]; // 遍历赋值 for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[0].length; j++) { arr[i][j] = s.nextInt(); } } // 遍历打印 for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[0].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println(); }

两类二维结构的主要差异可以归纳如下:

维度原生 2D 数组int[][]2D ArrayListArrayList<ArrayList<T>>
行数声明时固定,不可变动态,可随时增删
列数/行长固定(矩形),不可变每行独立、长度可变
初始默认值数值类型自动为 0/false元素默认无(需显式 add 行)
访问性能连续内存、直接下标寻址,更快多层引用间接寻址,有装箱与扩容开销
内存开销较高(每行一个对象 + 自动装箱 Integer 对象)
类型安全基本类型可直接存放泛型不能直接用基本类型,需用包装类
灵活度高,适合“行数/列数动态变化”的场景

从复杂度角度看:原生 2D 数组的随机访问是严格 O(1),2D ArrayList 的get(row)与内层get(col)同样是 O(1)(底层为数组索引),但由于存在引用链与潜在的扩容搬移,常数因子更大;而行/列的插入与删除在 2D ArrayList 中可能触发整行(或整列)的元素搬移,代价为 O(n)。

五、优点与缺点总结

综合原文档的定位与上述源码分析,2D ArrayList 的优劣可以归纳如下。

优点

  • 无需预定义尺寸:行与列均可按需增长,天然适配“数据规模未知、逐步累积”的读取流程;
  • 行结构独立:支持“参差不齐”的二维结构(每行列数不同),而原生二维数组必须是规则的矩形;
  • 丰富的内置 APIaddremoveindexOfcontainstoString等方法开箱即用,配合Collections工具类还能对每行排序、反转,代码表达力强;
  • 调试友好:整体println即可输出二维结构,便于快速验证中间结果。

缺点

  • 性能开销:泛型只能使用引用类型,基本类型元素会经历自动装箱(autoboxing),频繁读写时产生对象创建与 GC 压力;多层 ArrayList 的引用间接寻址也慢于连续内存的原生数组;
  • 内存占用:每个内层列表都是一个独立对象,外加装箱产生的Integer实例,内存密度显著低于int[][]
  • 代码冗余:声明与初始化需要显式逐行new ArrayList<>(),比int[][]的一行声明繁琐;
  • 误用风险:未初始化行就访问get(i)会抛异常;行删除导致的下标前移、遍历时修改结构等行为需要开发者格外谨慎。

选型建议:数据规模已知且固定、性能敏感的数值计算(如矩阵运算),优先选择原生int[][];而行数/列数动态变化、需要频繁插入删除整行、或对可读性与调试便利性要求更高的场景(如构建邻接表、表格型数据处理),2D ArrayList 是更合适的选择。

六、实践要点与注意事项

结合仓库示例与 Java 集合框架的行为,在项目中使用 2D ArrayList 时建议遵循以下实践:

  1. 先建行、后填格:任何arrLL.get(row)调用前,必须确保该行已被add(new ArrayList<>())创建,否则越界;
  2. 注意包装类陷阱ArrayList<Integer>中比较元素是否相等应使用equals(或Integer的缓存范围 -128~127 之外不要依赖==),contains/indexOf内部已按equals处理,可直接使用;
  3. 删除行后下标前移remove(row)之后,原 row+1 及其后的行整体前移,遍历删除时应从后往前删,避免漏删或越界;
  4. 预估容量减少扩容:若已知大致行数与行内元素规模,可在构造时传入初始容量(如new ArrayList<>(rows)),减少扩容搬移次数;
  5. 优先用增强 for 或迭代器遍历:遍历时若需增删元素,使用显式Iterator并调用iterator.remove(),避免ConcurrentModificationException

2D ArrayList 是理解“容器组合”与“动态多维结构”的最佳入门示例,也是许多复杂数据模型(如图的邻接表、动态表格)的构建基石。掌握其声明方式、逐行初始化的关键步骤与常用 API 的二维语义,就能在合适的场景中用它替代原生二维数组,写出更灵活、更易维护的 Java 代码。

【免费下载链接】cosmosWorld's largest Contributor driven code dataset | Used in Quark Search Engine, @OpenGenus IQ, OpenGenus Visual Project项目地址: https://gitcode.com/gh_mirrors/co/cosmos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询