背景
通过函数式编程中的map、filter 、reduce可以让我们更容易将代码中的控制逻辑和业务逻辑分离,代码的可读性也更高,jdk8之后,通过Stream函数可以进行map、filter 、reduce操作,例如对一个集合,将每个数先乘以3后,在将乘以3后的偶数进行求和,代码的示例如下
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i + 1);
}
int total = list.stream()
.map(i -> i * 3)
.filter(i -> i % 2 == 0)
.reduce(Integer::sum).get();
System.out.println(total);
//reduce(Integer::sum).get()也可以直接替换成sum()
查看stream的实现,还是有些逻辑,的下面就介绍一种更为简洁,性能实际上也更高的一种实现
更简单的实现stream
步骤一:定义元接口
@FunctionalInterface
public interface Pipeline<T> {
void each(Consumer<T> consumer);
}
你没看错,元接口的定义非常简单,就是一个只有each方法接口(@FunctionalInterface不是必须得,只是像说明这是一个函数接口),后续所以得实现,都通过这个接口来进行扩展
有了这接口,应该可以进行类似Stream的初始化了,代码如下
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i + 1);
}
Pipeline pipeline = list:foreach
我们也可以为Pipeline增加一个通过数组初始化的静态方法(jdk8 接口就支持静态方法了)
static <T> Pipeline<T> of(T... args) {
return c -> {
for (T arg : args) {
c.accept(arg);
}
};
}
步骤二:增加map方法
public interface Pipeline<T> {
void each(Consumer<T> consumer);
default <R> Pipeline<R> map(Function<T, R> function) {
return c -> each(t -> c.accept(function.apply(t)));
}
}
如果理解了基于数组的of方法,对map方法稍微想一想应该还是可以理解的,实际上就是重新创建了一个Pipeline,但是对于新的Pipeline,留过的值已经被function转换过了
步骤三:增加filter方法
default Pipeline<T> filter(Predicate<T> predicate) {
return c -> each(t -> {
if (predicate.test(t)) {
c.accept(t);
}
});
}
filter方法依然是返回一个新的Pipeline,但是只有需要的数据才留到新的Pipeline
步骤四:增加reduce方法
default <I, R> R reduce(Supplier<I> begin, BiConsumer<I, T> action, Function<I, R> end) {
I beginObj = begin.get();
each(t -> action.accept(beginObj, t));
return end.apply(beginObj);
}
reduce的核心就是三个步骤
- begin:在数据处理前进行初始化
- action:对每一条进行处理
- end:数据都处理结束了,做处理返回
基于这个reduce方法,我们就可以进一步实现一些常用的reduce方法了
例如先来一个java重常用的collect(Collectors.toList())
default List<T> reduceToList() {
return reduce(ArrayList::new, ArrayList::add, Function.identity());
}
在举复杂一点的,假设数据四数值类型,求平均值
default double reduceToAverage() {
return reduce(() -> new Object[]{0.0, 0}
, (arr, t) -> {
arr[0] = (Double) arr[0] + (Double) t;
arr[1] = (Integer) arr[1] + 1;
}, arr -> (Double) arr[0] / (Integer) arr[1]);
}
当然reduceToAverage只有类型为数值类型的时候才应该有这个方法,为了使用是上更简洁,我们可以新定义类型IntPipeline,DoublePipeline(继承Pipeline)这样的接口来提高专用的方法,如果通过泛型加继承解决读者可以试着想一想
进阶,实现findFirst
要实现FindFirst,关键是要进行中断,会了FindFirst的实现,anyMatch,allMatch就比较好实现了,findFirst实现如下
建立中断异常类
public class StopException extends RuntimeException{
public static final INSTANCE = new StopException();
@Override
public synchronized Throwalbe fillStackTrace(){
return this;
}
}
提供中断方法
static stop(){
throw StopException.INSTANCE
}
提供可中断的each方法
default void stopableEach(Consumer consumer){
try{
each(consumer)
}catch(StopException ignore){
}
}
实现findFirst方法
default Optional<T> findFirst(Predicate<T> predicate){
AtomicReference<T> ref = new AtomicReference();
stopableEach(t->{
if(predicate.test(t)){
ref.set(t);
stop();
}
});
return Optional.ofNullable(ref.get());
}
总结
通过上面的描述, 我们可以用更简单的方式来实现jdk的Stream,Stream的其他方法大家可以试着实现一些、下,对于并发也是可以支持的。关于流式编程的细节后续会进行分享