在Java中使用List的List

2023/06/07

1. 概述

List是Java中非常常用的数据结构。有时候,我们可能会因为某些需求需要一个嵌套的List结构,比如List<List<T>>。

在本教程中,我们将仔细研究这个“List的List”数据结构并探索一些日常操作。

2. List<T>[]与List<List<T>>

我们可以将“List的List”数据结构视为一个二维矩阵。因此,如果我们想要对多个List<T>对象进行分组,我们有两个选择:

  • 基于数组:List<T>[]
  • 基于列表:List<List<T>>

接下来,让我们看看什么时候选择哪个。

数组对于在O(1)时间内运行的“get”和“set”操作速度很快。但是,由于数组的长度是固定的,因此调整数组大小以插入或删除元素的代价很高

另一方面,List在插入和删除操作上更加灵活,这些操作在O(1)时间内运行。一般来说,List在“get/set”操作上比数组慢。但是一些List实现,例如ArrayList,在内部是基于数组的。因此,通常情况下,Array和ArrayList在“get/set”操作上的性能差异并不明显。

因此,在大多数情况下,我们会选择List<List<T>>数据结构以获得更好的灵活性

当然,如果我们开发的是一个性能关键型应用程序,并且我们不更改第一个维度的大小-例如,我们从不添加或删除内部列表,我们可以考虑使用List<T>[]结构。

本教程中,我们将主要讨论List<List<T>>。

3. List<List<T>>的常用操作

现在,让我们探讨一下List<List<T>>上的一些日常操作。

为简单起见,我们将操作List<List<String>>对象并在单元测试方法中验证结果。

此外,为了直接看到变化,我们还创建一个方法来打印List<List<T>>的内容:

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);
    });
}

接下来,我们先初始化一个List<List<T>>。

3.1 初始化List<List<T>>

我们将CSV文件中的数据导入List<List<T>>对象中,下面是CSV文件的内容:

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

假设我们将文件命名为example.csv并将其放在resources/listoflists目录下。

接下来,让我们创建一个方法来读取文件并将数据存储在List<List<T>>对象中:

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;
}

在getListOfListsFromCsv方法中,我们首先将CSV文件中的所有行读取到List<String>对象中。然后,我们遍历List行并将每一行(String)转换为List<String>。

最后,我们将每个转换后的List<String>对象添加到listOfLists中。因此,我们初始化了一个List<List<T>>。

好奇的人可能已经发现我们将Arrays.asList(..)包装在一个new ArrayList<>()中。这是因为Arrays.asList方法将创建一个不可变的List。但是,稍后我们将对内部列表进行一些更改。因此,我们将它包装在一个新的ArrayList对象中。

如果正确创建List<List<T>>对象,我们应该在外部列表中包含三个元素,即CSV文件中的行数。

此外,每个元素都是一个内部列表,每个元素都应包含四个元素。接下来,让我们编写一个单元测试方法来验证这一点。此外,我们将打印初始化的List<List<T>>:

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

接下来,让我们对listOfLists对象进行一些更改。但是,首先,让我们看看如何将更改应用于外部列表。

3.2 将更改应用于外部列表

如果我们关注外部列表,我们可以先忽略内部列表。换句话说,我们可以将List<List<String>>视为常规List<T>

因此,更改常规List对象并不是一个挑战。我们可以调用List的方法,例如add和remove来操作数据。

接下来,让我们向外部列表添加一个新元素:

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<String>对象。如上面的方法所示,我们创建了一个newList列表。然后,我们将新列表添加到listOfLists中index = 2的位置。

同样,在断言之后,我们打印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 将更改应用到内部列表

最后,让我们看看如何操作内部列表。

由于listOfList是一个嵌套的List结构,我们需要首先导航到我们要更改的内部列表对象。如果确切地知道索引,我们可以简单地使用get方法:

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

但是,如果我们想对所有内部列表应用更改,我们可以通过循环或Stream API传递List<List<T>>对象。

接下来,让我们看一个从listOfLists对象中删除所有“DeleteMe”条目的示例:

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);

正如我们在上面的方法中看到的,我们通过listOfLists.forEach{…}迭代每个内部列表,并使用Lambda表达式从innerList中删除“DeleteMe”条目。

如果我们执行测试,它会通过并生成以下输出:

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

4. 总结

在本文中,我们讨论了List<List<T>>数据结构。

此外,我们还通过示例解决了List<List<T>>的常见操作。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章