Filtering and Transforming Collections in Guava – 在Guava中过滤和转换集合

最后修改: 2014年 10月 16日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll illustrate how to filter and transform collections with Guava.

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

We will filter using Predicates, transform using the Functions that the library provides and finally, we’ll see how to combine both filtering and transforming.

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

2. Filter a Collection

2.过滤一个集合

Let’s start with a simple example of filtering a collection. We’ll be using an out of the box Predicate provided by the library and constructed via the Predicates utility class:

让我们从一个简单的过滤集合的例子开始。我们将使用一个由库提供的开箱即用的谓词,并通过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"));
}

As you can see, we’re filtering the List of names to get only the names which contain the character “a” – and we’re using Iterables.filter() to do it.

正如你所看到的,我们正在过滤名字的List,以便只获得包含字符 “a “的名字–我们使用Iterables.filter()来做到这一点。

Alternatively, we can make good use of Collections2.filter() API as well:

另外,我们也可以好好利用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());
}

A few things to note here – first, the output of Collections.filter() is a live view of the original collection – changes to one will be reflected in the other.

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

It’s also important to understand that now, the result is constrained by the predicate – if we add an element that doesn’t satisfy that Predicate, an IllegalArgumentException will be thrown:

同样重要的是,现在,结果受到谓词的约束–如果我们添加的元素不满足该Predicate,将抛出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. Write Custom Filter Predicate

3.编写自定义过滤器Predicate

Next – let’s write our own Predicate instead of using one provided by the library. In the following example – we’ll define a predicate that only gets the names that start with “A” or “J”:

接下来–让我们编写自己的Predicate,而不是使用库中提供的一个。在下面的例子中–我们将定义一个谓词,只获取以 “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. Combine Multiple Predicates

4.结合多个谓词

We can Combine Multiple Predicates using Predicates.or() and Predicates.and().
In the following example – we filter a List of names to get the names that start with “J” or don’t contain “a”:

我们可以使用Predicates.or()Predicates.and()合并多个谓词。
在下面的例子中–我们过滤一个List的名字,得到以 “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. Remove Null Values While Filtering a Collection

5.在过滤一个集合时去除空值

We can clean up the null values from a collection by filtering it with Predicates.notNull() as in the following example:

我们可以通过使用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. Check If All Elements in a Collection Match a Condition

6.检查一个集合中的所有元素是否符合一个条件

Next, let’s check if all elements in a Collection match a certain condition. We’ll use Iterables.all() to check if all names contain “n” or “m”, then we’ll check if all elements contain “a”:

接下来,让我们检查一个集合中的所有元素是否符合某个条件。我们将使用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. Transform a Collection

7.改造一个系列

Now – let’s see how to transform a collection using a Guava Function. In the following example – we transform a List of names to a List of Integers (length of the name) with Iterables.transform():

现在–让我们看看如何使用Guava的Function对一个集合进行转换。在下面的例子中–我们用Iterables.transform()将一个名字的List转换成IntegersList(名字的长度)。

@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));
}

We can also use the Collections2.transform() API as in the following example:

我们也可以使用 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());
}

Note that the output of Collections.transform() is a live view of the original Collection – changes to one affect the other.

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

And – same as before – if we try to add an element to the output Collection, an UnsupportedOperationException will be thrown.

而且–和以前一样–如果我们试图向输出的Collection添加一个元素,将会抛出一个UnsupportedOperationException

8. Create Function from Predicate

8.从Predicate创建Function

We can also create Function from a Predicate using Functions.fromPredicate(). This is, of course, going to be a function that transforms the inputs to Boolean, according to the condition of the predicate.

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

In the following example, we transform a List of names to a list of booleans where each element represents if the name contains “m”:

在下面的例子中,我们将一个名字的List转换为一个布尔列表,其中每个元素代表该名字是否包含 “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. Composition of Two Functions

9.两个函数的组成

Next – let’s take a look at how to transform a Collection using a composed Function.

接下来–让我们看看如何使用组成的Function来转换一个集合。

Functions.compose() returns the Composition of Two Functions as it applies the second Function on the output of the first Function.

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

In the following example – the first Function transform the name into its length, then the second Function transforms the length to a boolean value which represents if the name’s length is even:

在下面的例子中,第一个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. Combine Filtering and Transforming

10.结合过滤和转换

And now – let’s see another cool API that Guava has – one that will actually allow us to chain filtering and transforming together – the FluentIterable.

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

In the following example – we filter the List of names then transform it using FluentIterable:

在下面的例子中–我们过滤名字的List,然后使用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));
}

It is worth mentioning that, in some cases, the imperative version is more readable and should be preferred to the functional approach.

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

11. Conclusion

11.结论

Finally, we learned how to filter and transform collections using Guava. We used the Collections2.filter() and Iterables.filter() APIs for filtering, as well as Collections2.transform() and Iterables.transform() to transform collections.

最后,我们学习了如何使用Guava来过滤和转换集合。我们使用Collections2.filter()Iterables.filter() API进行过滤,以及使用Collections2.transform()Iterables.transform()来转换集合。

Finally, we took a quick look at the very interesting FluentIterable fluent API to combine both filtering and transforming.

最后,我们快速看了一下非常有趣的FluentIterable流畅API,以结合过滤和转换。

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现可以在GitHub项目中找到 – 这是一个基于Maven的项目,所以应该很容易导入和运行它。