当前教程
使用集合工厂方法创建和处理数据
系列中的下一篇

使用集合工厂方法创建和处理数据

 

创建不可变集合

Java SE 9 添加了一组工厂方法到 ListSet 接口,用于创建列表和集合。模式非常简单:只需调用 List.of()Set.of() 静态方法,传递列表和集合的元素,就完成了。

List<String> stringList = List.of("one", "two", "three");
Set<String> stringSet = Set.of("one", "two", "three");

不过,有几点值得注意。

  • 您得到的实现可能因您放入列表或集合中的元素数量而异。它们都不是 ArrayListHashSet,因此您的代码不应依赖于此。

  • 您得到的列表和集合都是不可变结构。您不能在其中添加或修改元素,也不能修改这些元素。如果这些结构的对象是可变的,您仍然可以修改它们。

  • 这些结构不接受空值。如果您尝试在这样的列表或集合中添加一个 null 值,您将得到一个异常。

  • Set 接口不允许重复:这就是集合的意义所在。因为创建具有重复值的集合没有意义,所以假设编写这样的代码是一个错误。因此,如果您尝试这样做,您将得到一个异常。

  • 您得到的实现是 Serializable

这些 of() 方法通常被称为集合的便利工厂方法

 

获取集合的不可变副本

继集合的便利工厂方法取得成功之后,Java SE 10 中添加了另一组便利方法,用于创建集合的不可变副本。

它们有两个:List.copyOf()Set.copyOf()。它们都遵循相同的模式

Collection<String> strings = Arrays.asList("one", "two", "three");

List<String> list = List.copyOf(strings);
Set<String> set = Set.copyOf(strings);

在所有情况下,您需要复制的集合都不应为空,也不应包含任何空元素。如果此集合具有重复项,则在 Set.copyOf() 的情况下,只会保留其中一个元素。

您得到的返回值是作为参数传递的集合的不可变副本。因此,修改此集合不会反映在您作为副本得到的列表或集合中。

您得到的任何实现都不接受 null 值。如果您尝试复制包含 null 值的集合,您将得到一个 NullPointerException

 

将数组包装在列表中

集合框架有一个名为 Arrays 的类,其中包含大约 200 个方法来处理数组。它们中的大多数是在数组上实现各种算法,例如排序、合并、搜索,在本节中不予介绍。

不过,有一个值得一提:Arrays.asList()。此方法将可变参数作为参数,并返回您传递的元素的 List,保留它们的顺序。此方法不是集合的便利工厂方法的一部分,但仍然非常有用。

List 充当数组的包装器,并以相同的方式运行,这可能一开始有点令人困惑。设置数组的大小后,您无法更改它。这意味着您无法向现有数组添加元素,也无法从中删除元素。您所能做的就是用另一个元素(可能是空值)替换现有元素。

通过调用 Arrays.asList() 获得的 List 正是如此。

  • 如果您尝试添加或删除元素,您将得到一个 UnsupportedOperationException,无论您是直接这样做还是通过迭代器这样做。
  • 替换现有元素是可以的。

因此,此列表不是不可变的,但对您如何更改它有限制。

 

使用集合工厂类处理集合

集合框架附带另一个工厂类:Collections,它有一组方法来操作集合及其内容。此类中大约有 70 个方法,逐个检查它们会很繁琐,因此让我们介绍其中的一部分。

从集合中提取最小值或最大值

Collections 类为您提供了两种方法:min()max()。这两种方法都将集合作为参数,从中提取最小值或最大值。这两种方法都有一个重载,它还将比较器作为另一个参数。

如果没有提供比较器,则集合的元素必须实现 Comparable。如果没有,将引发 ClassCastException。如果提供了比较器,则将使用它来获取最小值或最大值,无论集合的元素是否可比较。

使用此方法获取空集合的最小值或最大值将引发 NoSuchMethodException

在列表中查找子列表

两种方法可以在更大的列表中定位给定的子列表

更改列表元素的顺序

几种方法可以更改列表元素的顺序

  • sort() 对列表进行就地排序。此方法可以接受比较器作为参数。像往常一样,如果没有提供比较器,则列表的元素必须是可比较的。如果提供了比较器,则将使用它来比较元素。从 Java SE 8 开始,您应该优先使用 sort() 方法,该方法来自 List 接口。
  • shuffle() 随机打乱提供的列表的元素。如果您需要可以重复的随机打乱,可以提供您自己的 Random 实例。
  • rotate() 旋转列表的元素。旋转后,索引 0 处的元素将出现在索引 1 处,依此类推。最后一个元素将移动到列表的第一个位置。您可以将 subList()rotate() 结合起来,以删除给定索引处的元素,并将其插入列表中的另一个位置。这可以通过以下代码完成
List<String> strings = Arrays.asList("0", "1", "2", "3", "4");
System.out.println(strings);
int fromIndex = 1, toIndex = 4;
Collections.rotate(strings.subList(fromIndex, toIndex), -1);
System.out.println(strings);

结果如下

[0, 1, 2, 3, 4]
[0, 2, 3, 1, 4]

索引 fromIndex 处的元素已从其位置删除,列表已相应地重新组织,并且该元素已插入索引 toIndex - 1 处。

  • reverse(): 反转列表元素的顺序。
  • swap(): 交换列表中的两个元素。此方法可以接受列表作为参数,也可以接受普通数组。

将集合包装到不可变集合中

Collections 工厂类提供了多种方法来为您的集合或映射创建不可变包装器。结构的内容不会被复制;您得到的是围绕您的结构的包装器。所有尝试修改它的操作都会引发异常。

所有这些方法都以 unmodifiable 开头,后面跟着您的结构类型的名称。例如,要创建列表的不可变包装器,您可以调用

List<String> strings = Arrays.asList("0", "1", "2", "3", "4");
List<String> immutableStrings = Collections.unmodifiableList(strings);

警告:无法通过此包装器修改您的集合。但此包装器由您的集合支持,因此如果您通过其他方式修改它,此修改将反映在不可变集合中。让我们在以下代码中看到这一点

List<String> strings = new ArrayList<>(Arrays.asList("0", "1", "2", "3", "4"));
List<String> immutableStrings = Collections.unmodifiableList(strings);
System.out.println(immutableStrings);
strings.add("5");
System.out.println(immutableStrings);

运行此示例将为您提供以下内容

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]

如果您计划使用此模式创建不可变集合,则先防御性地复制它可能是一种安全的预防措施。

将集合包装到同步集合中

与您可以为您的映射和集合创建不可变包装器的方式相同,Collections 工厂类可以为它们创建同步包装器。这些模式遵循与创建不可变包装器的方法名称相同的命名约定:这些方法称为 synchronized 后面跟着 CollectionListSet 等...

您需要遵循两个预防措施。

  • 对您的集合的所有访问都应通过您获得的包装器进行。
  • 使用迭代器或流遍历您的集合应由调用代码在列表本身进行同步。

不遵循这些规则会导致您的代码出现竞争条件。

使用 Collections 工厂方法同步集合可能不是您的最佳选择。Java Util Concurrent 框架提供了更好的解决方案。


上次更新: 2021 年 9 月 14 日


当前教程
使用集合工厂方法创建和处理数据
系列中的下一篇