Groovy 和顺序集合 (JEP-431)
作者: Paul King
发布时间: 2023-04-29 09:00AM
由于 Groovy 旨在与 JDK 库紧密协作,因此一旦 JDK21 发布,Groovy 将“免费”获得新功能。但是,Groovy 有自己解决 JEP-431 所解决的一些问题的方案,因此这篇文章将介绍现有功能(您可以将其与较旧的 JDK 一起使用)以及一旦 JDK21 普遍可用且您升级到该 JDK 版本后可以使用的新功能。
这篇文章中的示例使用 JDK21ea Build 20(2023/4/27)和 Groovy 4.0.11。虽然 EA 版本附带了许多免责声明,警告在正式发布之前可能会发生功能更改或删除,但我们预计这些示例适用于后续版本。如果出现任何变化,我们会发布更新。
顺序集合摘要
顺序集合添加了三个新的接口:SequencedSet
、SequencedCollection
和 SequencedMap
。每个接口都添加了一些新方法,我们将在后面的示例中遇到。在本帖子的其余部分,我们将通过查看处理集合时可能遇到的各种场景来解释新的顺序集合功能。
访问第一个和最后一个元素
JEP-431 之前的 JDK
对于各种集合类型,访问第一个和最后一个元素这样简单的事情并不一致,也不像预期的那样容易(因此有了 JEP-431)。以下是一些您在此场景中使用的 JDK API 调用的示例
集合类型 | 第一个元素 | 最后一个元素 |
---|---|---|
|
|
|
|
|
|
|
|
需要遍历集合 |
|
|
|
JEP-431 之后的 JDK
JEP-431 之后,这有了很大的改进
集合类型 | 第一个元素 | 最后一个元素 |
---|---|---|
|
|
|
JEP-431 之前的 Groovy
Groovy 为数组和任何 Iterable
提供扩展方法 first()
和 last()
,在有意义的情况下提供各种优化版本。下标运算符适用于任何具有 getAt
方法的类。对于集合、数组和许多其他类,都有内置的实现。
聚合类型 | 第一个元素 | 最后一个元素 |
---|---|---|
|
|
|
Groovy 还提供了取扩展方法,可以在这里使用。您可以使用 list.take(1)
获取一个仅包含原始列表第一个元素的列表,并使用 takeRight(1)
获取一个仅包含最后一个元素的列表。这些方法也适用于所有不同的聚合类型。
JEP-431 之后的 Groovy
尚未为 JEP-431 添加特殊支持。因此 Groovy 功能将是现有功能加上新的 JDK 功能。
聚合类型 | 第一个元素 | 最后一个元素 |
---|---|---|
数组 |
|
|
|
|
|
目前,Groovy 的方法在数组(以及可能的其他类)中也提供了一致性。对于这种情况,人们不应急于使用 JEP-431 功能,但有一个重要的注意事项。随着时间的推移,可能会出现其他集合类型,这些类型可能为 getFirst()
或 getLast()
提供更有效的实现,在这种情况下,使用这些方法将是有益的。
未来的 Groovy 版本可能会提供专门的顺序集合支持。first()
和 last()
扩展方法可能会根据 getFirst()
和 getLast()
来实现。我们也可能会将顺序集合方法扩展到数组(以及其他类),但目前这不是优先事项。
示例
List list = [1, 2, 3]
assert list.get(0) == 1
assert list[0] == 1
assert list.first() == 1
assert list.getFirst() == 1 // NEW
assert list.first == 1 // NEW
assert list.get(list.size() - 1) == 3
assert list[-1] == 3
assert list.last() == 3
assert list.getLast() == 3 // NEW
assert list.last == 3 // NEW
LinkedList deque = [1, 2, 3]
assert deque[0] == 1
assert deque.first() == 1
assert deque.getFirst() == 1 // NEW
assert deque.first == 1 // NEW
assert deque[-1] == 3
assert deque.last() == 3
assert deque.getLast() == 3 // NEW
assert deque.last == 3 // NEW
LinkedHashSet set = [1, 2, 3]
assert set.iterator().next() == 1
assert set[0] == 1
assert set.first() == 1
assert set.getFirst() == 1 // NEW
assert set.first == 1 // NEW
assert set[-1] == 3
assert list.last() == 3
assert set.getLast() == 3 // NEW
assert set.last == 3 // NEW
TreeSet sortedSet = [2, 4, 1, 3]
assert sortedSet[0] == 1
assert sortedSet.first() == 1
assert sortedSet.getFirst() == 1 // NEW
assert sortedSet.first == 1 // NEW
assert sortedSet[-1] == 4
assert sortedSet.last() == 4
assert sortedSet.getLast() == 4 // NEW
assert sortedSet.last == 4 // NEW
Integer[] array = [1, 2, 3]
assert array[0] == 1
assert array.first() == 1
assert array[-1] == 3
assert array.last() == 3
删除第一个或最后一个元素
如果您需要修改集合,删除第一个或最后一个元素,Groovy 不会为所有聚合类型提供一致的扩展方法。您可以使用 JDK remove(0)
方法从 List
中删除列表的第一个元素(Groovy 还提供了一个不错的 removeAt(0)
别名)。Groovy 还为列表提供了 removeLast()
。鉴于此,SequencedCollection
中的 removeFirst()
和 removeLast()
方法是一个很好的补充。
如果您想创建一个与原始集合相同的新聚合,但删除第一个(或最后一个)元素,Groovy 提供了 tail()
和 drop(1)
(或 init()
和 dropRight(1)
)。
在开头/结尾添加元素
如果您需要修改集合,在开头或结尾添加元素,Groovy 不会为所有聚合类型提供一致的扩展方法。您通常会使用 add(element)
或 add(0, element)
来操作列表。因此,SequencedCollection
中的 addFirst()
和 addLast()
方法是一个很好的补充。Groovy 确实提供了 leftShift
运算符(<<
)作为另一种将元素追加到列表末尾的方法。
使用反转的集合
JEP-431 解决的另一个领域是改进与集合反向顺序进行交互的一致性。Groovy 已经为这种情况提供了一些增强功能,包括 reverse
、reverseEach
和 asReversed
扩展方法。但是,功能并不通用,有时会让人措手不及。reverse
方法不适用于映射和集合。您需要使用例如集合的迭代器。此外,标准 reverse
会产生一个新的集合(或数组),并且有一个可选的布尔参数,该参数使该方法成为一个修改操作——就地反转自身。这与 JEP-431 中的 reversed()
和 asReversed()
形成对比,后者返回一个视图。此外,reverseEach
和 asReversed
仅适用于 NavigableSet
实例。
因此,总而言之,JEP-431 提供的此功能非常受欢迎。
集合类型 | JEP-431 之前 | JEP-431 之后 | Groovy |
---|---|---|---|
|
使用 |
|
|
|
使用 |
|
|
|
使用 |
|
|
|
N/A |
|
|
示例
var result = []
list.reverseEach { result << it }
assert result == [3, 2, 1]
assert list.asReversed() == [3, 2, 1]
assert list.reverse() == [3, 2, 1]
assert list.reversed() == [3, 2, 1] // NEW
result = []
deque.reverseEach { result << it }
assert result == [3, 2, 1]
assert deque.asReversed() == [3, 2, 1]
assert deque.reverse() == [3, 2, 1]
assert deque.reversed() == [3, 2, 1] // NEW
result = []
assert set.iterator().reverse().toList() == [3, 2, 1]
assert set.reversed() == [3, 2, 1] as Set // NEW
result = []
sortedSet.reverseEach { result << it }
assert result == [4, 3, 2, 1]
assert sortedSet.asReversed() == [4, 3, 2, 1] as Set
assert sortedSet.reversed() == [4, 3, 2, 1] as Set // NEW
var map = [a: 1, b: 2]
result = []
map.reverseEach { k, v -> result << [k, v] }
assert result == [['b', 2], ['a', 1]]
assert map.reversed() == [b:2, a:1] // NEW
其他参考资料
-
JDK21 中即将推出的功能摘要(Infoworld 上的 Paul Krill)
-
Inside Java 新闻播报 #45(与 Nicolai 合作)
-
Inside Java 播客第 31 集(Ana-Maria Mihalceanu 与 Stuart Marks 合作)
-
https://www.infoq.com/news/2023/03/collections-framework-makeover/(InfoQ 上的 A N M Bazlur Rahman)
结论
我们快速浏览了使用 JEP-431 功能与 Groovy 的方法。虽然 Groovy 已经提供了一些 JEP-431 提供的功能,但它看起来确实是对 JDK 的一个很好的补充。