Java初学者不可不看的List.subList方法踩坑小记
2023-09-01 07:36:33
揭秘 Java List.subList 方法的陷阱:初学者指南
陷阱一:警惕数组越界
当您使用 List.subList 方法时,务必确保 fromIndex 和 toIndex 参数在 List 范围内。超出范围的索引会导致 IndexOutOfBoundsException 异常。为了避免此陷阱,请务必先检查索引范围。
代码示例:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// fromIndex 小于 0
try {
list.subList(-1, 2);
} catch (IndexOutOfBoundsException e) {
System.out.println("fromIndex 必须非负:" + e.getMessage());
}
// toIndex 大于 list.size()
try {
list.subList(0, 4);
} catch (IndexOutOfBoundsException e) {
System.out.println("toIndex 必须小于或等于 list.size():" + e.getMessage());
}
陷阱二:小心堆栈溢出
递归调用 subList 方法可能会导致堆栈溢出异常。如果方法不断创建新的子列表,就会发生这种情况。为了避免此陷阱,请避免递归调用 subList。
代码示例:
public void printSubList(List<Integer> list) {
if (list.isEmpty()) {
return;
}
// 递归调用
printSubList(list.subList(1, list.size()));
System.out.println(list.get(0));
}
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 递归调用导致堆栈溢出
printSubList(list);
陷阱三:避免空指针异常
如果您试图在空 List 上调用 subList,或者 resulting子列表为空,您会遇到空指针异常。为了避免此陷阱,请务必先检查 List 是否为 null,并使用 List.isEmpty() 方法检查子列表是否为空。
代码示例:
List<Integer> list = null;
// list 为 null
try {
list.subList(0, 2);
} catch (NullPointerException e) {
System.out.println("List 不能为 null:" + e.getMessage());
}
List<Integer> list = new ArrayList<>();
// 子列表为空
try {
list.subList(0, 2);
} catch (IndexOutOfBoundsException e) {
System.out.println("子列表为空:" + e.getMessage());
}
陷阱四:警惕内存泄漏
subList 仍然指向原始 List,即使原始 List 被清除。这可能会导致内存泄漏。为了避免此陷阱,请在不再需要时使用 subList.clear() 清除子列表。
代码示例:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 创建子列表
List<Integer> subList = list.subList(0, 2);
// 清除原始列表
list.clear();
// 由于 subList 仍然指向原始列表,因此元素不会被垃圾回收
陷阱五:并发安全问题
在多线程环境中使用 subList 时,请注意并发修改异常。如果其他线程修改了原始 List,您可能会遇到此异常。为了避免此陷阱,请在多线程环境中使用 Collections.synchronizedList() 创建一个同步的 List。
代码示例:
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
list.add(2);
list.add(3);
Thread thread1 = new Thread(() -> {
// 线程 1 添加元素到原始列表
list.add(4);
});
Thread thread2 = new Thread(() -> {
// 线程 2 提取子列表
List<Integer> subList = list.subList(0, 2);
// 线程 2 尝试访问子列表中的元素
System.out.println(subList.get(0));
});
thread1.start();
thread2.start();
最佳实践
遵循这些最佳实践可以避免在使用 List.subList 方法时踩坑:
- 检查索引范围
- 避免递归调用
- 处理空 List 和空子列表
- 清除不再需要的子列表
- 在多线程环境中使用同步 List
常见问题解答
-
如何检查 fromIndex 和 toIndex 范围?
您可以使用 List.size() 来获取 List 的长度并检查索引是否在范围内。 -
为什么会出现堆栈溢出异常?
递归调用 subList 方法会导致堆栈溢出异常。避免递归调用可以解决此问题。 -
如何避免空指针异常?
在使用 subList 方法之前,请检查 List 是否为 null 并使用 List.isEmpty() 检查子列表是否为空。 -
如何防止内存泄漏?
在不再需要子列表时使用 subList.clear() 来清除其元素。 -
为什么在多线程环境中需要同步 List?
在多线程环境中,同步 List 可以防止并发修改异常。