Stream API三巨头:filter、map、collect
我们来深入剖析 Stream API 中最核心、最常用的“三巨头”:filter
、map
和 collect
。理解了它们,你就掌握了 Stream 大半的精髓。
1. filter()
- 过滤器
是什么?
filter
是一个中间操作。它就像一个大筛子或一个过滤器,用于从流中筛选出满足特定条件的元素。
怎么工作?
- 参数:它接受一个
Predicate<? super T>
函数式接口作为参数。Predicate
是一个“断言函数”,它接收一个参数,返回一个boolean
值(true
或false
)。
- 逻辑:流中的每一个元素都会被传递给
Predicate
的test
方法。- 如果
test
方法返回true
,该元素会被保留并传递到下一个操作。 - 如果返回
false
,该元素会被过滤掉。
- 如果
- 返回值:返回一个新的
Stream<T>
,包含所有被保留的元素。
代码示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Anna", "Edward");// 过滤出所有以字母 "A" 开头的名字
List<String> namesStartingWithA = names.stream().filter(name -> name.startsWith("A")) // Predicate: 输入name,返回boolean.collect(Collectors.toList());System.out.println(namesStartingWithA); // 输出: [Alice, Anna]// 过滤出长度大于 3 的名字
List<String> longNames = names.stream().filter(name -> name.length() > 3).collect(Collectors.toList());System.out.println(longNames); // 输出: [Alice, Charlie, David, Anna, Edward]
2. map()
- 映射/转换器
是什么?
map
是一个中间操作。它就像一个加工机床,用于将流中的每个元素转换成另一个形式。它是一对一的映射,原元素会被新元素替换。
怎么工作?
- 参数:它接受一个
Function<? super T, ? extends R>
函数式接口作为参数。Function
是一个“转换函数”,它接收一个参数(类型T
),返回一个结果(类型R
)。
- 逻辑:流中的每一个元素都会被传递给
Function
的apply
方法,并将该方法的返回值(可以是任何类型)组成一个新的流。 - 返回值:返回一个
Stream<R>
,流的元素类型可能已经从T
改变为了R
。
代码示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");// 1. 将每个字符串映射为它的长度(将 String 转换为 Integer)
List<Integer> nameLengths = names.stream().map(name -> name.length()) // Function: 输入String,返回Integer.collect(Collectors.toList());
System.out.println(nameLengths); // 输出: [5, 3, 7]// 2. 将每个字符串转换为大写
List<String> upperCaseNames = names.stream().map(String::toUpperCase) // 使用方法引用,等价于 .map(s -> s.toUpperCase()).collect(Collectors.toList());
System.out.println(upperCaseNames); // 输出: [ALICE, BOB, CHARLIE]// 更复杂的例子:从对象中提取特定字段
class Person {private String name;private int age;// 省略构造方法和getter/setter
}
List<Person> people = Arrays.asList(new Person("Alice", 25),new Person("Bob", 30)
);// 将 Person 流映射为 String 流(只包含名字)
List<String> onlyNames = people.stream().map(person -> person.getName()) // 输入Person,输出String.collect(Collectors.toList());
System.out.println(onlyNames); // 输出: [Alice, Bob]// 将 Person 流映射为 Integer 流(只包含年龄)
List<Integer> onlyAges = people.stream().map(Person::getAge) // 使用方法引用.collect(Collectors.toList());
System.out.println(onlyAges); // 输出: [25, 30]
3. collect()
- 收集器/终结者
是什么?
collect
是一个终结操作。它是流水线的终点站,用于将流中的元素聚合、汇总到一个容器中(如 List, Set, Map)或生成一个值。它会触发整个流的实际执行。
怎么工作?
- 参数:它接受一个
Collector<? super T, A, R>
接口的参数。- 幸运的是,我们很少自己实现这个接口,而是使用
Collectors
工具类提供的各种静态工厂方法。
- 幸运的是,我们很少自己实现这个接口,而是使用
- 逻辑:它根据传入的
Collector
策略,对流中的元素进行可变缩减(mutable reduction),将元素累积到一个结果容器中。 - 返回值:返回一个类型为
R
的结果,通常是一个集合或一个值。
常用 Collectors 方法示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Anna", "Bob");// 1. 收集到 List (最常用)
List<String> list = names.stream().filter(name -> name.length() > 3).collect(Collectors.toList()); // 返回 ArrayList// 2. 收集到 Set (自动去重)
Set<String> set = names.stream().collect(Collectors.toSet()); // 返回 HashSet, 输出: [Alice, Bob, Charlie, David, Anna] (只有一个Bob)// 3. 收集到指定的集合类型,例如 TreeSet
TreeSet<String> treeSet = names.stream().collect(Collectors.toCollection(TreeSet::new)); // 可以指定集合的具体实现// 4. 连接字符串:将所有元素用分隔符连接起来
String joined = names.stream().collect(Collectors.joining(", ")); // 参数是分隔符
System.out.println(joined); // 输出: Alice, Bob, Charlie, David, Anna, Bob// 5. 分组:按条件分组,返回一个 Map
// 按名字的首字母分组
Map<Character, List<String>> groupByFirstLetter = names.stream().collect(Collectors.groupingBy(name -> name.charAt(0)));
System.out.println(groupByFirstLetter);
// 输出: {A=[Alice, Anna], B=[Bob, Bob], C=[Charlie], D=[David]}// 6. 分区:是分组的一种特例,按true/false分区(满足条件和不满足条件的)
// 将名字分为长度大于3和不超过3的两部分
Map<Boolean, List<String>> partitioned = names.stream().collect(Collectors.partitioningBy(name -> name.length() > 3));
System.out.println(partitioned);
// 输出: {false=[Bob, Bob], true=[Alice, Charlie, David, Anna]}
组合使用:威力无穷
真正的威力在于将它们组合成一条声明式的流水线。
任务:有一个名字列表,要求:
- 过滤掉长度小于等于3的。
- 将所有名字转换为大写。
- 按字母顺序排序。
- 收集到一个新的
ArrayList
中。
List<String> names = Arrays.asList("Chris", "Alice", "Bob", "David", "Eve");List<String> processedNames = names.stream() // 获取流.filter(name -> name.length() > 3) // 1. 过滤.map(String::toUpperCase) // 2. 转换.sorted() // 3. 排序.collect(Collectors.toList()); // 4. 收集System.out.println(processedNames); // 输出: [ALICE, CHRIS, DAVID]
总结对比
方法 | 类型 | 目的 | 参数 | 返回值 |
---|---|---|---|---|
filter | 中间操作 | 筛选元素 | Predicate<T> (返回boolean) | Stream<T> |
map | 中间操作 | 转换元素 | Function<T, R> (T->R) | Stream<R> |
collect | 终结操作 | 收集元素到容器 | Collector<? super T, A, R> | R (通常是一个集合) |
记住这个流程:获取流 -> 过滤(filter) -> 转换(map) -> 收集(collect)
。这个模式可以解决你日常开发中绝大部分的数据处理需求。多练习,你就会越来越习惯这种声明式的优雅编程风格。