java stream的简洁实现

文章介绍了如何通过自定义的Pipeline接口来实现类似于Java8Stream的功能,包括map、filter和reduce操作。Pipeline接口简化了函数式编程的逻辑,提供了更简洁的代码实现,同时也支持数组初始化和一些常用方法如reduceToList和reduceToAverage。此外,还展示了如何实现findFirst方法以及中断机制。
摘要由CSDN通过智能技术生成

背景

通过函数式编程中的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的其他方法大家可以试着实现一些、下,对于并发也是可以支持的。关于流式编程的细节后续会进行分享

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值