目录
reduce模式
前面介绍过很多lamabda表达式的用法,传送门
现在介绍一种比较通用的编程模式,reduce:reduce 操作可以实现从一组值中生成一个值。比如因为常用而被纳入标准库中
count、 min 和 max 方法
@Test
public void test1() {
// count测试
List<Integer> list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
// 计算集合中值大于3的个数
long result = list.stream().filter(v -> v> 3).count();
assert result == 2;
// 找出集合中最大值
Integer maxV = list.stream().max(Integer::compareTo).get();
assert maxV == 5;
// 找出集合中最大值
Integer minV = list.stream().min(Integer::compareTo).get();
assert minV == 1;
}
这些方法都是 reduce 操作。
再看一个例子,现在要将上面的数据里面的值进行求和,即1+2+3+4+5=?
如果是传统的写法怎么做呢,用for循环!
@Test
public void test2() {
// 求和
List<Integer> list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
Integer total = 0;
for (Integer v : list) {
total = total + v;
}
assert total == 15;
}
再看一下reduce的写法,Lambda 表达式就是 reducer, 它执行求和操作, 有两个参数: 传入 Stream 中的当前元素和 acc。 将两个参数相加, acc 是累加器, 保存着当前的累加结果。
total = list.stream().reduce(0, (acc, ele) -> acc + ele).intValue();
assert total == 15;
定制收集器
常见的需求
一个工单审核流程中,把审批人员名字,格式化输出
传统的for循环写法
@Test
public void test4() {
List<String> names = Arrays.asList("张三","李四","王五");
StringBuffer sb = new StringBuffer();
sb.append("[");
for (String name : names) {
if (sb.length() > 1) {
sb.append(", ");
sb.append(name);
}
}
sb.append("]");
System.out.println(sb);
}
输出:[张三, 李四, 王五]
Lambda写法
public void test5() {
List<String> names = Arrays.asList("张三","李四","王五");
StringBuffer sb = new StringBuffer();
sb.append("[");
names.stream().forEach(name -> {
if (sb.length() > 1) {
sb.append(", ");
}
sb.append(name);
});
sb.append("]");
System.out.println(sb);
}
用了lambda的forEach,不过在这里,forEach还是有点重了,没有达到用高级收集器简化代码的目的,再用reduce试试
reduce写法
public void test6() {
List<String> names = Arrays.asList("张三", "李四", "王五");
StringBuffer sb = names.stream().reduce(new StringBuffer(), (builder, name) -> {
if (builder.length() > 1) {
builder.append(", ");
}
builder.append(name);
return builder;
}, (left, right) ->
left.append(right)
);
sb.insert(0, "[");
sb.append("]");
System.out.println(sb);
}
上面3种写法都能实现结果,但是不知道有没有发现,lambda表达式,不管是forEach,reduce并没有使代码易读,反而理难理解了。所以定制一个收集器来实现这个需求
定制收集器
实现 Collector 接口
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
public class StringCollector implements Collector<String, StringJoiner, String> {
private String delimiter;
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public String getDelimiter() {
return delimiter;
}
public String getSuffix() {
return suffix;
}
public StringCollector(String delimiter, String prefix, String suffix) {
this.delimiter = delimiter;
this.prefix = prefix;
this.suffix = suffix;
}
/**
* A function that creates and returns a new mutable result container.
*
* @return a function which returns a new, mutable result container
*/
@Override
public Supplier<StringJoiner> supplier() {
return () -> new StringJoiner(delimiter, prefix, suffix);
}
/**
* A function that folds a value into a mutable result container.
*
* @return a function which folds a value into a mutable result container
*/
@Override
public BiConsumer<StringJoiner, String> accumulator() {
return StringJoiner::add;
}
/**
* A function that accepts two partial results and merges them. The
* combiner function may fold state from one argument into the other and
* return that, or may return a new result container.
*
* @return a function which combines two partial results into a combined
* result
*/
@Override
public BinaryOperator<StringJoiner> combiner() {
return StringJoiner::merge;
}
/**
* Perform the final transformation from the intermediate accumulation type
* {@code A} to the final result type {@code R}.
*
* <p>If the characteristic {@code IDENTITY_TRANSFORM} is
* set, this function may be presumed to be an identity transform with an
* unchecked cast from {@code A} to {@code R}.
*
* @return a function which transforms the intermediate result to the final
* result
*/
@Override
public Function<StringJoiner, String> finisher() {
return StringJoiner::toString;
}
/**
* Returns a {@code Set} of {@code Collector.Characteristics} indicating
* the characteristics of this Collector. This set should be immutable.
*
* @return an immutable set of collector characteristics
*/
@Override
public Set<Characteristics> characteristics() {
return new HashSet();
}
}
由于 Collector 接口支持泛型, 因此先得确定一些具
体的类型:
- 待收集元素的类型, 这里是 String;
- 累加器的类型 StringJoiner;
- 最终结果的类型, 这里依然是 String。
然后来使用它
@Test
public void test7() {
List<String> names = Arrays.asList("张三", "李四", "王五");
String result = names.stream().collect(new StringCollector(",","[", "]"));
System.out.println(result);
}
输出结果与前面一致:[张三,李四,王五]
到此,一个定制化的收集器就开发完成了。
一个收集器由四部分组成
- 首先是一个 Supplier, 这是一个工厂方法, 用来创建容器, 在这个例子中, 就是 StringJoiner。 和 reduce 操作中的第一个参数类似, 它是后续操作的初值
/**
* A function that creates and returns a new mutable result container.
*
* @return a function which returns a new, mutable result container
*/
@Override
public Supplier<StringJoiner> supplier() {
return () -> new StringJoiner(delimiter, prefix, suffix);
}
所以在StringCollector声明需要传入的参数:分割符-delimiter,前缀prefix,后缀-suffix及对应的构造函数
private String delimiter;
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public String getDelimiter() {
return delimiter;
}
public String getSuffix() {
return suffix;
}
public StringCollector(String delimiter, String prefix, String suffix) {
this.delimiter = delimiter;
this.prefix = prefix;
this.suffix = suffix;
}
- 收集器的 accumulator 的作用和 reduce 操作的第二个参数一样, 它结合之前操作的结果和当前值, 生成并返回新的值
/**
* A function that folds a value into a mutable result container.
*
* @return a function which folds a value into a mutable result container
*/
@Override
public BiConsumer<StringJoiner, String> accumulator() {
return StringJoiner::add;
}
- combine 方法很像 reduce 操作的第三个方法。 如果有两个容器, 我们需要将其合并
/**
* A function that accepts two partial results and merges them. The
* combiner function may fold state from one argument into the other and
* return that, or may return a new result container.
*
* @return a function which combines two partial results into a combined
* result
*/
@Override
public BinaryOperator<StringJoiner> combiner() {
return StringJoiner::merge;
}
- 上面已经将流中的值叠加入一个可变容器中, 但这还不是想要的最终结果。 这里调用了 finisher 方法, 以便进行转换。
/** * Perform the final transformation from the intermediate accumulation type * {@code A} to the final result type {@code R}. * * <p>If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an * unchecked cast from {@code A} to {@code R}. * * @return a function which transforms the intermediate result to the final * result */ @Override public Function<StringJoiner, String> finisher() { return StringJoiner::toString; }
- 特征是一组描述收集器的对象, 框架可以对其适当优化。 characteristics 方法定义了特征
/** * Returns a {@code Set} of {@code Collector.Characteristics} indicating * the characteristics of this Collector. This set should be immutable. * * @return an immutable set of collector characteristics */ @Override public Set<Characteristics> characteristics() { return new HashSet(); }