策划的网站,信用惠州网站建设,万能浏览器网页版,关键词挖掘爱站网在Java中创建二维 ArrayList#xff08;即嵌套列表#xff09;的方法有多种#xff0c;下面我将详细介绍常用的几种方式#xff0c;并分析它们的区别和适用场景。 1. 使用嵌套 ArrayList 创建二维列表
方法一#xff1a;直接嵌套 ArrayList
这是最常用的方法#xff0c…在Java中创建二维 ArrayList即嵌套列表的方法有多种下面我将详细介绍常用的几种方式并分析它们的区别和适用场景。 1. 使用嵌套 ArrayList 创建二维列表
方法一直接嵌套 ArrayList
这是最常用的方法创建一个 ArrayList每个元素本身又是一个 ArrayList从而形成二维结构。
示例代码
import java.util.ArrayList;public class TwoDArrayListExample {public static void main(String[] args) {// 创建二维 ArrayListArrayListArrayListInteger twoDList new ArrayList();// 初始化第一行ArrayListInteger row1 new ArrayList();row1.add(1);row1.add(2);row1.add(3);// 初始化第二行ArrayListInteger row2 new ArrayList();row2.add(4);row2.add(5);row2.add(6);// 添加行到二维列表twoDList.add(row1);twoDList.add(row2);// 输出二维列表System.out.println(twoDList);}
}输出
[[1, 2, 3], [4, 5, 6]]优点
灵活可以处理不规则的二维结构每行的长度可以不同。易于理解和实现。
缺点
需要手动管理每一行的初始化和添加代码较繁琐。访问元素的时间复杂度稍高因为 ArrayList 是基于动态数组实现的扩展时可能涉及数组复制。 方法二在循环中动态初始化
这种方法通过循环来自动生成多行多列适用于需要预定义尺寸的二维列表。
示例代码
import java.util.ArrayList;public class DynamicTwoDArrayList {public static void main(String[] args) {int rows 3, cols 4;ArrayListArrayListInteger twoDList new ArrayList();// 初始化二维 ArrayListfor (int i 0; i rows; i) {ArrayListInteger row new ArrayList();for (int j 0; j cols; j) {row.add(i * cols j); // 填充数据}twoDList.add(row);}// 输出二维列表System.out.println(twoDList);}
}输出
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]优点
适合处理规则的矩阵结构代码简洁。便于初始化大规模数据。
缺点
固定行列数量灵活性不如直接嵌套 ArrayList。 2. 使用 ListListT 接口实现
虽然 ArrayList 是最常用的实现但使用 List 接口定义可以增加代码的通用性和灵活性便于将来切换到其他 List 实现如 LinkedList。
示例代码
import java.util.List;
import java.util.ArrayList;public class ListInterfaceExample {public static void main(String[] args) {ListListString twoDList new ArrayList();ListString row1 new ArrayList();row1.add(A);row1.add(B);ListString row2 new ArrayList();row2.add(C);row2.add(D);twoDList.add(row1);twoDList.add(row2);System.out.println(twoDList);}
}输出
[[A, B], [C, D]]优点
代码更加通用便于后期维护。如果将来需要换成 LinkedList 或其他 List 实现可以直接替换代码无需大改。
缺点
性能和功能上与直接使用 ArrayList 差异不大主要优势体现在代码结构上。 3. 使用 Arrays.asList() 快速初始化
Arrays.asList() 可以用于快速初始化嵌套 ArrayList适用于静态、已知数据的二维列表。
示例代码
import java.util.ArrayList;
import java.util.Arrays;public class AsListExample {public static void main(String[] args) {ArrayListArrayListInteger twoDList new ArrayList(Arrays.asList(new ArrayList(Arrays.asList(1, 2, 3)),new ArrayList(Arrays.asList(4, 5, 6)),new ArrayList(Arrays.asList(7, 8, 9))));System.out.println(twoDList);}
}输出
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]优点
初始化快速适合处理已知静态数据。代码简洁明了。
缺点
数据固定不适合需要动态修改的场景。Arrays.asList() 返回的列表大小固定无法增加或删除元素除非包裹在新的 ArrayList 中。 4. 使用 Collections.nCopies() 创建固定大小的二维列表
如果需要创建一个固定大小的二维 ArrayList 并填充默认值可以使用 Collections.nCopies()。
示例代码
import java.util.ArrayList;
import java.util.Collections;public class NCopiesExample {public static void main(String[] args) {int rows 3, cols 4;ArrayListArrayListInteger twoDList new ArrayList(Collections.nCopies(rows, new ArrayList(Collections.nCopies(cols, 0))));System.out.println(twoDList);}
}输出
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]注意事项
上述代码实际上会让每一行都引用同一个 ArrayList 实例修改一行会影响所有行。因此需要深拷贝来避免这个问题。
正确版本
import java.util.ArrayList;
import java.util.Collections;public class CorrectNCopiesExample {public static void main(String[] args) {int rows 3, cols 4;ArrayListArrayListInteger twoDList new ArrayList();for (int i 0; i rows; i) {twoDList.add(new ArrayList(Collections.nCopies(cols, 0)));}System.out.println(twoDList);}
}方法对比总结
方法优点缺点适用场景直接嵌套 ArrayList灵活、易于理解适合不规则数据初始化和管理代码较繁琐小型项目或动态行列数据循环动态初始化适合大规模数据代码简洁行列固定灵活性较差规则矩阵结构使用 ListListT 接口代码通用便于维护和扩展与直接嵌套 ArrayList 差异不大需要考虑代码扩展性或可替换性的场景Arrays.asList() 快速初始化初始化快速代码简洁数据固定无法动态增删元素静态数据初始化Collections.nCopies()快速创建固定大小的二维列表需要深拷贝避免引用问题稍复杂创建统一默认值的矩阵 推荐使用场景
规则矩阵如棋盘、表格数据使用循环动态初始化。静态、已知数据使用 Arrays.asList() 进行快速初始化。动态修改、不规则数据直接嵌套 ArrayList灵活管理行列。 将二维数组转换为二维列表的方法总结 1. 使用嵌套 for-each 循环
适用场景适用于简单的遍历和转换适合处理基本数据类型或对象数组。
代码示例基本数据类型
import java.util.ArrayList;public class ForEachExample {public static void main(String[] args) {int[][] array {{1, 2, 3},{4, 5, 6},{7, 8, 9}};ArrayListArrayListInteger list new ArrayList();for (int[] row : array) {ArrayListInteger tempList new ArrayList();for (int num : row) {tempList.add(num);}list.add(tempList);}System.out.println(list); // 输出: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
}注意事项
适用于规则或不规则的数组结构。不适合处理需要索引访问的场景因为 for-each 循环不提供索引信息。 2. 使用 Stream.forEach 流式处理
适用场景适用于大数据处理或需要链式调用的场景代码简洁且易于扩展。
代码示例基本数据类型
import java.util.ArrayList;
import java.util.Arrays;public class StreamForEachExample {public static void main(String[] args) {int[][] array {{1, 2, 3},{4, 5, 6},{7, 8, 9}};ArrayListArrayListInteger list new ArrayList();Arrays.stream(array).forEach(row - {ArrayListInteger tempList new ArrayList();Arrays.stream(row).forEach(tempList::add);list.add(tempList);});System.out.println(list); // 输出: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
}注意事项
forEach 用于流的遍历但不能修改流本身的结构。适合简单转换但对于复杂链式操作建议使用 map 和 collect。 3. 使用 Stream.map 和 collect推荐
适用场景更优雅的流式处理方式适合复杂数据转换或链式操作。
代码示例基本数据类型
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class StreamMapExample {public static void main(String[] args) {int[][] array {{1, 2, 3},{4, 5, 6},{7, 8, 9}};ListListInteger list Arrays.stream(array).map(row - Arrays.stream(row).boxed() // 将 int 转换为 Integer.collect(Collectors.toList())).collect(Collectors.toList());System.out.println(list); // 输出: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
}代码示例自定义类
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class OXOGame {public static void main(String[] args) {OXOPlayer[][] players {{new OXOPlayer(O)},{new OXOPlayer(X)}};ListListOXOPlayer playerList Arrays.stream(players).map(row - Arrays.stream(row).collect(Collectors.toList())).collect(Collectors.toList());playerList.forEach(row - {row.forEach(player - System.out.print(player.getPlayingLetter() ));System.out.println();});}
}class OXOPlayer {private String playingLetter;public OXOPlayer(String letter) {this.playingLetter letter;}public String getPlayingLetter() {return playingLetter;}
}注意事项
.boxed() 仅适用于基本数据类型处理自定义类时无需使用。使用 map 和 collect 可以实现链式操作代码更简洁易读。 4. 使用 flatMap 展平为一维列表进阶用法
适用场景当需要将二维数组转换为一维列表时使用。
代码示例自定义类展平
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class FlatMapExample {public static void main(String[] args) {OXOPlayer[][] players {{new OXOPlayer(O), new OXOPlayer(X)},{new OXOPlayer(X), new OXOPlayer(O)}};ListOXOPlayer flatList Arrays.stream(players).flatMap(Arrays::stream).collect(Collectors.toList());flatList.forEach(player - System.out.print(player.getPlayingLetter() ));// 输出: O X X O}
}class OXOPlayer {private String playingLetter;public OXOPlayer(String letter) {this.playingLetter letter;}public String getPlayingLetter() {return playingLetter;}
}注意事项
flatMap 会将嵌套的流展平成单层流适合处理需要扁平化的数据结构。展平后无法保留原有的二维结构适合一维数据处理。 总结
方法适用场景代码简洁性是否支持链式操作备注嵌套 for-each 循环简单遍历适合初学者一般否适用于简单数据转换但代码较冗长Stream.forEach简单流式处理适合中等规模数据较简洁否不支持复杂链式操作适用于遍历但不适合结构修改Stream.map collect推荐方法适合复杂数据转换和链式操作非常简洁是支持高级流操作适用于自定义类和基本数据类型flatMap 展平处理将二维数组转换为一维列表适合扁平化需求简洁是适合需要将嵌套结构扁平化处理的场景 在 Java 中你提到的这个情况其实是合法的因为它使用的是嵌套类Nested Class而不是定义多个顶层 public class。 1. 嵌套类的定义
在 Java 中可以在一个类的内部定义另一个类这种内部类可以是 public、protected、private 或 default 访问级别。内部类包括
静态嵌套类Static Nested Class非静态内部类Inner Class局部内部类Local Class匿名内部类Anonymous Class 2. 你的代码属于静态嵌套类
你的代码中的 OutsideCellRangeException 是 OXOMoveException 的静态嵌套类这是合法且常见的设计模式特别是在定义特定异常子类时。
示例代码
public class OXOMoveException extends RuntimeException {Serial private static final long serialVersionUID 1;public OXOMoveException(String message) {super(message);}// 定义枚举类型用于表示行或列的错误public enum RowOrColumn { ROW, COLUMN }// 静态嵌套类表示单元格超出范围的异常public static class OutsideCellRangeException extends OXOMoveException {public OutsideCellRangeException(String message) {super(message);}}
}3. 为什么可以在 public 类中定义 public 静态嵌套类 访问控制不同于顶层类在 Java 中一个 .java 文件中只能有一个 public 的顶层类文件名必须与该类名一致。然而嵌套类不受此限制可以在 public 顶层类中定义多个 public 嵌套类或枚举。 嵌套类的命名空间嵌套类属于外部类的命名空间OutsideCellRangeException 被视为 OXOMoveException.OutsideCellRangeException而不是独立的顶层类。 4. 如何使用嵌套类
你可以直接通过外部类访问嵌套的 OutsideCellRangeException 类
public class Main {public static void main(String[] args) {try {throw new OXOMoveException.OutsideCellRangeException(Cell is outside the valid range.);} catch (OXOMoveException e) {System.out.println(e.getMessage());}}
}输出
Cell is outside the valid range.静态嵌套类的特点不依赖外部类实例静态嵌套类不需要外部类的实例可以直接通过外部类的名称访问。
内部类和嵌套类的区别
类型是否需要外部类实例可使用的访问修饰符常见用途静态嵌套类不需要public, protected, private, 默认定义工具类、异常类等非静态内部类需要public, protected, private, 默认访问外部类实例的成员变量局部内部类需要无局部作用域内有效定义在方法或代码块内临时使用的类匿名内部类需要无简化接口或抽象类的快速实现 误解认为一个 public 类中不能有另一个 public 类。 解释在同一个 .java 文件中确实不能有两个 public 顶层类但嵌套类内部类不受此限制可以在一个 public 类中定义多个 public 嵌套类。 总结你的前两个问题
1. 第一个问题异常定义的方式与 OXOGame 的兼容性 你的异常定义 你将所有异常类定义为 OXOMoveException 的 内部静态类static class。例如 public static class InvalidBoardSizeException extends OXOMoveException {public InvalidBoardSizeException() {super(Board size is larger than 9x9 or smaller than 3x3);}
}在调用时需要写成 throw new OXOMoveException.InvalidBoardSizeException();出现的问题 未处理异常的报错 当你在 OXOController 中抛出异常时OXOGame 并没有显式的 try-catch 来捕获这个异常导致 IDE 提示“未处理异常”。异常信息显示问题 OXOGame 中虽然捕获了 OXOMoveException但显示的错误信息可能是异常类的全名如 OXOMoveException$InvalidBoardSizeException而不是你定义的具体错误消息。 原因 你的异常继承自 Exception属于 检查型异常Checked ExceptionJava 强制要求在调用的地方处理使用 try-catch 或在方法签名中 throws。OXOGame 中默认使用 exception.toString() 输出异常而 toString() 默认返回的是类名而不是异常消息。 2. 第二个问题如何在不修改 OXOGame 的前提下解决异常处理 解决方法 将异常从检查型异常改为非检查型异常 将 OXOMoveException 改为继承 RuntimeException使其成为 非检查型异常Unchecked Exception这样在 OXOGame 中调用时不需要显式处理异常IDE 也不会报错。 public class OXOMoveException extends RuntimeException {public OXOMoveException(String message) {super(message);}
}重写 toString() 方法 为了确保 OXOGame 捕获异常时能正确显示自定义的错误信息重写 toString() 方法让它返回 getMessage() Override
public String toString() {return getMessage(); // 确保打印异常时显示的是消息而不是类名
}最终效果 OXOGame 不需要任何修改OXOController 中抛出的异常会被正确捕获并显示详细的错误信息。控制台输出 Game move exception: Board size is larger than 9x9 or smaller than 3x3异常定义对项目的影响 检查型异常 vs 非检查型异常 检查型异常Checked Exception 继承自 Exception必须显式处理。如果不在调用的地方加 try-catch 或 throwsIDE 会报错。影响 会强制你修改调用该异常的方法或类违背题目不修改 OXOGame 的要求。 非检查型异常Unchecked Exception 继承自 RuntimeException不需要显式处理。即使调用的地方没有 try-catch程序仍能正常运行异常在运行时自动处理。影响 可以在不修改 OXOGame 的情况下正确抛出和捕获异常符合题目要求。 内部类 vs 外部类异常定义 内部类异常如 OXOMoveException.InvalidBoardSizeException 结构更紧凑便于组织代码但在引用时路径较长可能导致代码可读性降低。 外部类异常如 InvalidBoardSizeException extends OXOMoveException 结构清晰引用简便更符合常规的异常定义习惯。 super 和 getMessage() 方法的解释 super(message) 的作用 super(message) 是调用父类构造函数的方法。在自定义异常中调用 super(message) 将错误信息传递给 Exception 或 RuntimeException 的构造函数保存到异常对象内部。示例 public InvalidBoardSizeException() {super(Board size is larger than 9x9 or smaller than 3x3);
}这表示创建 InvalidBoardSizeException 对象时错误消息 Board size is larger than 9x9 or smaller than 3x3 会被保存到异常对象中。 getMessage() 的作用 getMessage() 是 Throwable 类中的方法用于返回通过 super(message) 传入的错误信息。当你调用 exception.getMessage()会返回你定义的错误消息。示例 catch (OXOMoveException e) {System.out.println(e.getMessage()); // 输出Board size is larger than 9x9 or smaller than 3x3
}为什么需要重写 toString() 方法 默认情况下toString() 返回的是异常的类名和内存地址类似 edu.uob.OXOMoveException$InvalidBoardSizeException为了确保打印异常时显示自定义的错误信息可以重写 toString() 方法让它返回 getMessage() Override
public String toString() {return getMessage();
}这样即使调用的是 System.out.println(exception)也会输出具体的错误消息而不是类名。 总结
你的异常定义内部静态类是正确的但因为它们是 检查型异常Java 强制要求在调用时显式处理导致在 OXOGame 中无法直接兼容。将异常改为非检查型异常继承 RuntimeException 可以绕开显式处理的要求满足题目不修改 OXOGame 的限制。使用 super(message) 传递错误信息使用 getMessage() 获取错误信息重写 toString() 确保异常信息正确显示。