Groovy™ 和序列化集合 (JEP-431)

作者: Paul King

发布日期:2023-04-29 上午 09:00


JDK21 中一个令人兴奋的新功能是有序集合,它为具有已定义遭遇顺序的集合提供了改进的处理。有关新功能的更多详细信息,请参阅后面的其他参考资料部分。

由于 Groovy 的设计与 JDK 库紧密结合,一旦 JDK21 发布,Groovy 将“免费”获得新功能。然而,Groovy 针对 JEP-431 解决的一些问题有其自己的解决方案,因此本文将介绍现有功能(您可以与旧版 JDK 一起使用)以及一旦 JDK21 普遍可用并且您升级到该 JDK 版本后可以使用的新功能。

本文中的示例使用 JDK21ea Build 20 (2023/4/27) 和 Groovy 4.0.11。尽管 EA 版本附带了大量免责声明,警告在正式发布之前可能会发生功能更改或移除,但我们预计示例在后续版本中仍然有效。如果出现任何变化,我们将发布更新。

有序集合总结

有序集合添加了三个新接口:SequencedSetSequencedCollectionSequencedMap。每个接口都添加了一些新方法,我们将在后面的示例中遇到这些方法。在本文的其余部分,我们将通过查看您在处理集合时可能遇到的各种场景来解释新的有序集合功能。

访问第一个和最后一个元素

JEP-431 之前的 JDK

对于各种集合类型,像访问第一个和最后一个元素这样简单的操作并不一致或像预期的那样容易(因此有了 JEP-431)。以下是您在这种场景下将使用的 JDK API 调用的一些示例

集合类型 第一个元素 最后一个元素

列表

list.get(0)

list.get(list.size()-1)

双端队列

deque.getFirst()

deque.getLast()

集合

set.iterator().next()
set.stream().findFirst().get()

需要遍历集合

有序集合

set.first()

set.last()

JEP-431 之后的 JDK

JEP-431 之后,这方面有了很大改善

集合类型 第一个元素 最后一个元素

ListDequeSet

collection.getFirst()

collection.getLast()

JEP-431 之前的 Groovy

Groovy 为数组和任何 Iterable 提供了扩展方法 first()last(),并在合理的情况下提供了各种优化版本。对于任何具有 getAt 方法的类,都提供了下标运算符。集合、数组和许多其他类都有内置实现。

聚合类型 第一个元素 最后一个元素

ListDequeSet、数组

aggregate[0]
aggregate.first()

aggregate[-1]
aggregate.last()

Groovy 还提供了 take 扩展方法,可以在这里使用。您可以使用 list.take(1) 获取一个只包含原始列表第一个元素的列表,并使用 takeRight(1) 获取一个只包含最后一个元素的列表。这些方法也适用于所有不同的聚合类型。

JEP-431 之后的 Groovy

目前尚未为 JEP-431 添加特殊支持。因此,Groovy 功能将是现有功能加上新的 JDK 功能。

聚合类型 第一个元素 最后一个元素

数组

array[0]
array.first()

array[-1]
array.last()

ListDequeSet

collection[0]
collection.first()
collection.getFirst()
collection.first

collection[-1]
collection.last()
collection.getLast()
collection.last

目前,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 的 List 中的 remove(0) 方法从列表中移除第一个元素(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 已经为这种情况提供了一些增强功能,包括 reversereverseEachasReversed 扩展方法。然而,该功能并不通用,有时会让人感到困惑。reverse 方法不适用于映射和集合。您需要使用例如集合的迭代器。此外,标准的 reverse 会生成一个新集合(或数组),并且有一个可选的布尔参数使该方法成为一个可变操作——原地反转。这与 JEP-431 和 asReversed() 中的 reversed() 形成对比,后者返回一个视图。此外,reverseEachasReversed 仅适用于 NavigableSet 实例。

因此,总而言之,JEP-431 提供的这个功能非常受欢迎。

集合类型 JEP-431 之前 JEP-431 之后 Groovy

列表

使用 list.listIterator(list.size()).previous()

list.reversed()

list.reverseEach
list.reverse()
list.asReversed()

双端队列

使用 deque.descendingIterator()

deque.reversed()

deque.reverseEach
deque.reverse()
deque.asReversed()

可导航集合

使用 set.descendingSet()

set.reversed()

set.reverseEach
set.asReversed()

Set (其他)

不适用

set.reversed()

set.iterator().reverse()

示例

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

其他参考资料

结论

我们快速了解了如何在 Groovy 中使用 JEP-431 功能。虽然 Groovy 已经提供了一些 JEP-431 提供的功能,但它看起来确实是 JDK 的一个很好的补充。