1. 概述

List是Java中常用的数据结构。有时我们需要嵌套的List结构(如List<List<T>>)来满足特定需求。本文将深入探讨这种"List的List"数据结构,并演示常见操作技巧。

2. 数组 vs 嵌套List

我们可以将"List的List"视为二维矩阵。当需要组合多个List<T>对象时,有两种选择:

  • 数组实现List<T>[]
  • List实现List<List<T>>

选择建议

  • 数组get/set操作时间复杂度O(1),但长度固定,插入/删除元素成本高
  • List:插入/删除操作时间复杂度O(1),灵活性更高,get/set操作略慢(但ArrayList等实现基于数组,性能差异不明显)

踩坑提醒:除非在性能敏感场景且第一维度大小固定(如不增删内部List),否则优先选择List<List<T>>以获得更好的灵活性。本文重点讨论List<List<T>>

3. 嵌套List的常见操作

为方便演示,我们以List<List<String>>为例。先创建一个辅助方法打印嵌套List内容:

private void printListOfLists(List<List<String>> listOfLists) {
    System.out.println("\n           List of Lists          ");
    System.out.println("-------------------------------------");
    listOfLists.forEach(innerList -> {
        String line = String.join(", ", innerList);
        System.out.println(line);
    });
}

3.1 初始化嵌套List

从CSV文件导入数据到List<List<T>>。示例CSV内容(resources/listoflists/example.csv):

Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

读取文件并初始化的方法:

private List<List<String>> getListOfListsFromCsv() throws URISyntaxException, IOException {
    List<String> lines = Files.readAllLines(Paths.get(getClass().getResource("/listoflists/example.csv")
        .toURI()));

    List<List<String>> listOfLists = new ArrayList<>();
    lines.forEach(line -> {
        List<String> innerList = new ArrayList<>(Arrays.asList(line.split(", ")));
        listOfLists.add(innerList);
    });
    return listOfLists;
}

关键点:用new ArrayList<>()包装Arrays.asList(),避免返回不可变List(后续需要修改内部List)。

验证初始化结果:

List<List<String>> listOfLists = getListOfListsFromCsv();

assertThat(listOfLists).hasSize(3);
assertThat(listOfLists.stream()
  .map(List::size)
  .collect(Collectors.toSet())).hasSize(1)
  .containsExactly(4);

printListOfLists(listOfLists);

输出:

           List of Lists          
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

3.2 操作外层List

List<List<T>>视为普通List<T>操作即可。示例:在索引2处插入新List:

List<List<String>> listOfLists = getListOfListsFromCsv();
List<String> newList = new ArrayList<>(Arrays.asList("Slack", "Zoom", "Microsoft Teams", "Telegram"));
listOfLists.add(2, newList);

assertThat(listOfLists).hasSize(4);
assertThat(listOfLists.get(2)).containsExactly("Slack", "Zoom", "Microsoft Teams", "Telegram");

printListOfLists(listOfLists);

输出:

           List of Lists          
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Slack, Zoom, Microsoft Teams, Telegram
Delete Me, Mercurial, Git, Subversion

3.3 操作内层List

精准操作指定内层List

通过索引获取目标List后操作:

List<String> innerList = listOfLists.get(x);
// innerList.add(), remove() ...

批量操作所有内层List

使用Stream API或循环遍历。示例:删除所有"Delete Me"条目:

List<List<String>> listOfLists = getListOfListsFromCsv();

listOfLists.forEach(innerList -> innerList.remove("Delete Me"));

assertThat(listOfLists.stream()
    .map(List::size)
    .collect(Collectors.toSet())).hasSize(1)
    .containsExactly(3);

printListOfLists(listOfLists);

输出:

           List of Lists          
-------------------------------------
Linux, Microsoft Windows, Mac OS
Kotlin, Java, Python
Mercurial, Git, Subversion

3.4 计算嵌套List的尺寸

场景1:内层List的数量

直接调用size()

List<List<String>> listOfLists = getListOfListsFromCsv();
assertThat(listOfLists).hasSize(3);

场景2:所有内层List的元素总数

使用Stream API计算总和:

int totalElements = listOfLists.stream().mapToInt(List::size).sum();
assertThat(totalElements).isEqualTo(12);

简单粗暴mapToInt()将每个内层List转为其尺寸的IntStream,再调用sum()求和。

4. 总结

本文详细介绍了Java中嵌套List(List<List<T>>)的使用场景和核心操作:

  1. 选择依据:优先使用List<List<T>>而非数组实现,除非有特殊性能需求
  2. 初始化技巧:注意可变性处理,用ArrayList包装Arrays.asList()
  3. 操作分层
    • 外层List:直接使用标准List方法
    • 内层List:通过索引精准操作或Stream批量处理
  4. 尺寸计算:区分内层List数量和总元素数,后者用Stream API高效计算

掌握这些操作能显著提升处理复杂数据结构的效率,尤其在数据导入导出、表格处理等场景中。


原始标题:Working With a List of Lists in Java | Baeldung