Contents

Guava 中过滤和转换集合

1. 概述

在本教程中,我们将说明如何使用 Guava 过滤和转换集合

我们将使用Predicates 进行过滤,使用库提供的 Functions 进行转换,最后,我们将看到如何结合过滤和转换。

2. 过滤一个集合

让我们从一个过滤集合的简单示例开始。我们将使用库提供的开箱即用 Predicate,并通过Predicates实用程序类构建:

@Test
public void whenFilterWithIterables_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable<String> result 
      = Iterables.filter(names, Predicates.containsPattern("a"));
    assertThat(result, containsInAnyOrder("Jane", "Adam"));
}

如您所见,我们正在过滤名称list以仅获取包含字符“a”的名称 - 我们正在使用*Iterables.filter()*来执行此操作。

或者,我们也可以充分利用Collections2.filter() API:

@Test
public void whenFilterWithCollections2_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result 
      = Collections2.filter(names, Predicates.containsPattern("a"));
    
    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder("Jane", "Adam"));
    result.add("anna");
    assertEquals(5, names.size());
}

这里有几点需要注意——首先,*Collections.filter()*的输出是原始集合的实时视图——对一个集合的更改将反映在另一个集合中。

同样重要的是要理解现在,结果受到谓词的约束——如果我们添加一个不满足该谓词的元素,则会抛出IllegalArgumentException

@Test(expected = IllegalArgumentException.class)
public void givenFilteredCollection_whenAddingInvalidElement_thenException() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result 
      = Collections2.filter(names, Predicates.containsPattern("a"));
    result.add("elvis");
}

3. 编写自定义过滤谓词

接下来——让我们编写自己的谓词,而不是使用库提供的谓词。在下面的例子中——我们将定义一个只获取以“A”或“J”开头的名称的谓词:

@Test
public void whenFilterCollectionWithCustomPredicate_thenFiltered() {
    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("J");
        }
    };
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result = Collections2.filter(names, predicate);
    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam"));
}

4. 组合多个谓词

我们可以使用*Predicates.or()Predicates.and()*组合多个谓词。

在下面的示例中——我们过滤一个名称列表以获取以“J”开头或不包含“a”的名称:

@Test
public void whenFilterUsingMultiplePredicates_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result = Collections2.filter(names, 
      Predicates.or(Predicates.containsPattern("J"), 
      Predicates.not(Predicates.containsPattern("a"))));
    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Tom"));
}

5. 过滤集合时删除空值

我们可以通过使用Predicates.notNull()过滤集合来清除集合中的null值,如下例所示:

@Test
public void whenRemoveNullFromCollection_thenRemoved() {
    List<String> names = 
      Lists.newArrayList("John", null, "Jane", null, "Adam", "Tom");
    Collection<String> result = 
      Collections2.filter(names, Predicates.notNull());
    assertEquals(4, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam", "Tom"));
}

6.检查集合中的所有元素是否匹配条件

接下来,让我们检查一个 Collection 中的所有元素是否都匹配某个条件。我们将使用*Iterables.all()*检查所有名称是否包含“n”或“m”,然后我们将检查所有元素是否包含“a”:

@Test
public void whenCheckingIfAllElementsMatchACondition_thenCorrect() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    boolean result = Iterables.all(names, Predicates.containsPattern("n|m"));
    assertTrue(result);
    result = Iterables.all(names, Predicates.containsPattern("a"));
    assertFalse(result);
}

7. 转换集合

现在 - 让我们看看如何使用 Guava Function转换集合。在以下示例中——我们使用*Iterables.transform()*将名称列表转换为整数列表(名称的长度) :

@Test
public void whenTransformWithIterables_thenTransformed() {
    Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable<Integer> result = Iterables.transform(names, function);
    assertThat(result, contains(4, 4, 4, 3));
}

我们还可以使用Collections2.transform() API,如下例所示:

@Test
public void whenTransformWithCollections2_thenTransformed() {
    Function<String,Integer> func = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };
    List<String> names = 
      Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Integer> result = Collections2.transform(names, func);
    assertEquals(4, result.size());
    assertThat(result, contains(4, 4, 4, 3));
    result.remove(3);
    assertEquals(3, names.size());
}

请注意,Collections.transform()的输出是**原始Collection** 的实时视图——对一个集合的更改会影响另一个。

而且 - 和以前一样 - 如果我们尝试向输出Collection添加一个元素,则会抛出UnsupportedOperationException

8. Functions.fromPredicate()

我们还可以使用Functions.fromPredicate()从谓词创建函数。当然,这将是一个根据谓词的条件将输入转换为Boolean的函数。

在以下示例中,我们将名称列表转换为布尔值列表,其中每个元素表示名称是否包含“m”:

@Test
public void whenCreatingAFunctionFromAPredicate_thenCorrect() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Boolean> result =
      Collections2.transform(names,
      Functions.forPredicate(Predicates.containsPattern("m")));
    assertEquals(4, result.size());
    assertThat(result, contains(false, false, true, true));
}

9. 两个函数的组合

接下来——让我们看看如何使用组合的Function来转换 Collection 。

Functions.compose()返回两个函数的组合,因为它将第二个Function应用于第一个Function的输出。

在下面的示例中——第一个Function将名称转换为其长度,然后第二个Function将长度转换为一个Boolean值,该值表示名称的长度是否为偶数:

@Test
public void whenTransformingUsingComposedFunction_thenTransformed() {
    Function<String,Integer> f1 = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };
    Function<Integer,Boolean> f2 = new Function<Integer,Boolean>(){
        @Override
        public Boolean apply(Integer input) {
            return input % 2 == 0;
        }
    };
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Boolean> result = 
      Collections2.transform(names, Functions.compose(f2, f1));
    assertEquals(4, result.size());
    assertThat(result, contains(true, true, true, false));
}

10. 结合过滤和变换

现在——让我们看看 Guava 拥有的另一个很酷的 API——一个实际上允许我们将过滤和转换链接在一起的 API—— FluentIterable

在以下示例中 - 我们过滤名称列表,然后使用FluentIterable对其进行转换:

@Test
public void whenFilteringAndTransformingCollection_thenCorrect() {
    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("T");
        }
    };
    Function<String, Integer> func = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Integer> result = FluentIterable.from(names)
                                               .filter(predicate)
                                               .transform(func)
                                               .toList();
    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder(4, 3));
}

值得一提的是,在某些情况下,命令式版本更具可读性,应该优先于函数式方法。