Contents

在Groovy的集合中查找元素

1. 简介

Groovy 提供了大量增强 Java 核心功能的方法。

在本教程中,我们将展示 Groovy 在检查元素并在多种类型的集合 中找到它时如何做到这一点。

2. 测试元素是否存在

首先,我们将专注于测试给定集合是否包含元素。

2.1. 列表

Java 本身提供了几种使用java.util.List检查列表中项目的方法:

  • contains方法
  • indexOf方法

由于 Groovy 是一种与 Java 兼容的语言,我们可以安全地使用它们。

我们来看一个例子:

@Test
void whenListContainsElement_thenCheckReturnsTrue() {
    def list = ['a', 'b', 'c']
    assertTrue(list.indexOf('a') > -1)
    assertTrue(list.contains('a'))
}

除此之外,Groovy 还引入了成员操作符:

element in list

它是 Groovy 提供的众多语法糖运算符之一。在它的帮助下,我们可以简化我们的代码:

@Test
void whenListContainsElement_thenCheckWithMembershipOperatorReturnsTrue() {
    def list = ['a', 'b', 'c']
    assertTrue('a' in list)
}

2.2. Set

与前面的示例一样,我们可以使用java.util.Set#contains方法和in运算符:

@Test
void whenSetContainsElement_thenCheckReturnsTrue() {
    def set = ['a', 'b', 'c'] as Set
    assertTrue(set.contains('a'))
    assertTrue('a' in set)
}

2.3. Map

对于Map,我们可以直接检查键或值:

@Test
void whenMapContainsKeyElement_thenCheckReturnsTrue() {
    def map = [a: 'd', b: 'e', c: 'f']
    assertTrue(map.containsKey('a'))
    assertFalse(map.containsKey('e'))
    assertTrue(map.containsValue('e'))
}

或使用成员运算符来查找匹配键:

@Test
void whenMapContainsKeyElement_thenCheckByMembershipReturnsTrue() {
    def map = [a: 'd', b: 'e', c: 'f']
    assertTrue('a' in map)
    assertFalse('f' in map)
}

当与地图一起使用时,我们应该小心使用成员运算符,因为这个运算符与布尔值一起使用有点令人困惑。底层机制不是测试键的存在,而是从映射中检索相应的值并将其转换为布尔值:

@Test
void whenMapContainsFalseBooleanValues_thenCheckReturnsFalse() {
    def map = [a: true, b: false, c: null]
    assertTrue(map.containsKey('b'))
    assertTrue('a' in map)
    assertFalse('b' in map)
    assertFalse('c' in map)
}

正如我们在上面的例子中看到的那样,出于同样的原因,**使用null值也有点危险。**Groovy 将falsenull都转换为 boolean false

3. 所有匹配和任何匹配

在大多数情况下,我们处理由更复杂的对象组成的集合。在本节中,我们将展示如何检查给定集合是否包含至少一个匹配元素,或者是否所有元素都匹配给定谓词。

让我们从定义一个简单的类开始,我们将在整个示例中使用它:

class Person {
    private String firstname
    private String lastname
    private Integer age
    // constructor, getters and setters
}

3.1. List/Set

这一次,我们将使用一个简单的Person对象列表:

private final personList = [
  new Person("Regina", "Fitzpatrick", 25),
  new Person("Abagail", "Ballard", 26),
  new Person("Lucian", "Walter", 30),
]

正如我们之前提到的,Groovy 是一种与 Java 兼容的语言,所以我们首先使用Java 8 引入的Stream API 创建一个示例:

@Test
void givenListOfPerson_whenUsingStreamMatching_thenShouldEvaluateList() {
    assertTrue(personList.stream().anyMatch {it.age > 20})
    assertFalse(personList.stream().allMatch {it.age < 30})
}

我们还可以使用直接对集合执行检查的 Groovy 方法DefaultGroovyMethods#any和 DefaultGroovyMethods#every  :

@Test
void givenListOfPerson_whenUsingCollectionMatching_thenShouldEvaluateList() {
    assertTrue(personList.any {it.age > 20})
    assertFalse(personList.every {it.age < 30})
}

3.2. Map

让我们从定义由Person#firstname映射的 Person对象 的Map开始:

private final personMap = [
  Regina : new Person("Regina", "Fitzpatrick", 25),
  Abagail: new Person("Abagail", "Ballard", 26),
  Lucian : new Person("Lucian", "Walter", 30)
]

我们可以通过它的键、值或整个条目来评估它。同样,让我们首先使用Stream API:

@Test
void givenMapOfPerson_whenUsingStreamMatching_thenShouldEvaluateMap() {
    assertTrue(personMap.keySet().stream().anyMatch {it == "Regina"})
    assertFalse(personMap.keySet().stream().allMatch {it == "Albert"})
    assertFalse(personMap.values().stream().allMatch {it.age < 30})
    assertTrue(personMap.entrySet().stream().anyMatch
      {it.key == "Abagail" && it.value.lastname == "Ballard"})
}

然后是 Groovy 集合 API:

@Test
void givenMapOfPerson_whenUsingCollectionMatching_thenShouldEvaluateMap() {
    assertTrue(personMap.keySet().any {it == "Regina"})
    assertFalse(personMap.keySet().every {it == "Albert"})
    assertFalse(personMap.values().every {it.age < 30})
    assertTrue(personMap.any {firstname, person -> firstname == "Abagail" && person.lastname == "Ballard"})
}

正如我们所见,Groovy 不仅在操作地图时充分替代了Stream API,而且还允许我们直接对Map对象执行检查,而不是使用java.util.Map#entrySet方法。

4. 在集合中查找一个或多个元素

4.1. List/Set

我们还可以使用谓词提取元素。让我们从熟悉的Stream API 方法开始:

@Test
void givenListOfPerson_whenUsingStreamFind_thenShouldReturnMatchingElements() {
    assertTrue(personList.stream().filter {it.age > 20}.findAny().isPresent())
    assertFalse(personList.stream().filter {it.age > 30}.findAny().isPresent())
    assertTrue(personList.stream().filter {it.age > 20}.findAll().size() == 3)
    assertTrue(personList.stream().filter {it.age > 30}.findAll().isEmpty())
}

正如我们所看到的,上面的示例使用java.util.Optional来查找单个元素,因为Stream API 强制使用该方法。

另一方面,Groovy 提供了更紧凑的语法:

@Test
void givenListOfPerson_whenUsingCollectionFind_thenShouldReturnMatchingElements() {
    assertNotNull(personList.find {it.age > 20})
    assertNull(personList.find {it.age > 30})
    assertTrue(personList.findAll {it.age > 20}.size() == 3)
    assertTrue(personList.findAll {it.age > 30}.isEmpty())
}

通过使用 Groovy 的 API,我们可以跳过创建Stream 和过滤它

4.2. Map

Map的情况下,有几个选项可供选择。**我们可以在键、值或完整条目中找到元素。**由于前两个基本上是ListSet,因此在本节中,我们将仅展示查找条目的示例。

让我们重用之前的personMap

@Test
void givenMapOfPerson_whenUsingStreamFind_thenShouldReturnElements() {
    assertTrue(
      personMap.entrySet().stream()
        .filter {it.key == "Abagail" && it.value.lastname == "Ballard"}
        .findAny().isPresent())
    assertTrue(
      personMap.entrySet().stream()
        .filter {it.value.age > 20}
        .findAll().size() == 3)
}

同样,简化的 Groovy 解决方案:

@Test
void givenMapOfPerson_whenUsingCollectionFind_thenShouldReturnElements() {
    assertNotNull(personMap.find {it.key == "Abagail" && it.value.lastname == "Ballard"})
    assertTrue(personMap.findAll {it.value.age > 20}.size() == 3)
}

在这种情况下,好处更加显着。我们跳过java.util.Map#entrySet 方法并使用带有Map上提供的函数的闭包。