1. 概述
本文将介绍如何使用 Guava 来对集合进行 过滤(filter) 和 转换(transform) 操作。
我们将使用 Guava 提供的 Predicates 进行过滤,使用 Functions 进行转换,并展示如何将两者组合使用。
2. 集合过滤
先来看一个简单的集合过滤示例。我们将使用 Guava 提供的 Predicates
工具类创建的 Predicate
来进行过滤:
@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"));
}
上面这段代码过滤了 names
列表,只保留包含字符 "a"
的名字。使用的是 Iterables.filter()
方法。
当然,也可以使用 Collections2.filter()
:
@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());
}
⚠️ 注意:
Collections2.filter()
返回的是原始集合的 实时视图(live view),对结果集合的修改会影响原始集合。- 同时,由于结果集合被 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. 自定义过滤条件(Predicate)
除了使用 Guava 内置的 Predicate
,我们也可以自己实现。例如下面这个 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. 组合多个 Predicate
可以通过 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. 过滤掉集合中的 null 值
使用 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. 检查集合中所有元素是否满足条件
使用 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
可以对集合进行转换操作。下面的例子将名字列表转换为名字长度的列表:
@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()
:
@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());
}
⚠️ 注意:
Collections2.transform()
返回的也是原始集合的 实时视图。- 如果尝试向转换后的集合中添加元素,会抛出
UnsupportedOperationException
。
8. 由 Predicate 创建 Function
可以通过 Functions.forPredicate()
将 Predicate
转换为 Function
,该函数会将输入转换为布尔值:
@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. 函数组合
使用 Functions.compose()
可以将两个 Function
组合起来:第二个函数作用于第一个函数的输出。
下面的例子中,第一个函数获取名字长度,第二个判断长度是否为偶数:
@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:FluentIterable
,可以将过滤和转换操作串联起来。
下面的例子先过滤出以 "A"
或 "T"
开头的名字,再转换为名字长度:
@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));
}
✅ 虽然函数式风格很优雅,但在某些场景下,命令式代码可能更清晰,建议根据实际可读性权衡选择。
11. 总结
本文介绍了如何使用 Guava 对集合进行过滤和转换:
- 过滤:
Collections2.filter()
和Iterables.filter()
- 转换:
Collections2.transform()
和Iterables.transform()
- 组合操作:
FluentIterable
提供了链式调用的 API
所有示例代码均可在 GitHub 项目 中找到,这是一个基于 Maven 的项目,可直接导入运行。