使用集合工厂方法创建和处理数据
创建不可变集合
Java SE 9 添加了一组工厂方法到 List
和 Set
接口,用于创建列表和集合。模式非常简单:只需调用 List.of()
或 Set.of()
静态方法,传递列表和集合的元素,就完成了。
List<String> stringList = List.of("one", "two", "three");
Set<String> stringSet = Set.of("one", "two", "three");
不过,有几点值得注意。
您得到的实现可能因您放入列表或集合中的元素数量而异。它们都不是
ArrayList
或HashSet
,因此您的代码不应依赖于此。您得到的列表和集合都是不可变结构。您不能在其中添加或修改元素,也不能修改这些元素。如果这些结构的对象是可变的,您仍然可以修改它们。
这些结构不接受空值。如果您尝试在这样的列表或集合中添加一个
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
。
在列表中查找子列表
两种方法可以在更大的列表中定位给定的子列表
indexOfSublist(List<?> source, List<?> target)
:返回target
列表的第一个元素在source
列表中的第一个索引,如果不存在,则返回 -1;lastIndexOfSublist(List<?> source, List<?> target)
:返回这些索引中的最后一个。
更改列表元素的顺序
几种方法可以更改列表元素的顺序
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
处。
将集合包装到不可变集合中
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
后面跟着 Collection
、List
、Set
等...
您需要遵循两个预防措施。
- 对您的集合的所有访问都应通过您获得的包装器进行。
- 使用迭代器或流遍历您的集合应由调用代码在列表本身进行同步。
不遵循这些规则会导致您的代码出现竞争条件。
使用 Collections
工厂方法同步集合可能不是您的最佳选择。Java Util Concurrent 框架提供了更好的解决方案。
上次更新: 2021 年 9 月 14 日