|
相关链接
【目录】新流式编程(序)
各语言流式API的现状
支持流式API的语言其实不多,比较典型的代表是Java的Stream与Kotlin的Sequence(其实是我对这俩最熟。示例代码如下
Java流的示例
Stream.of(1, 2, 3, 4, 5)
.limit(4)
.map(i -> i * 2)
.filter(i -> i % 3 > 0)
.map(Object::toString)
.collect(Collectors.joining(","));以上代码流程为
- 首先生成一个1~5的流 -> 1, 2, 3, 4, 5
- 截取前4个 -> 1, 2, 3, 4
- 每个元素映射为原来的2倍 -> 2, 4, 6, 8
- 过滤掉能被3整除的数 -> 2, 4, 8
- 每个元素转为String -> "2", "4", "8"
- 合并所有元素 -> "2,4,8"
Kotlin的流式API与Java类似,只是部分名称稍有不同
sequenceOf(1, 2, 3, 4, 5)
.take(4)
.map { it * 2 }
.filter { it % 3 > 0 }
.map { it.toString() }
.joinToString(",")事实上,Java对流的实现依赖的是Spliterator,是一种特殊的Iterator,可以提供并发的额外好处。相比之下,Kotlin的实现是直接基于Iterator,要简单优雅很多。 为方便演示,后续的示例我主要还是用Java或者一些伪代码展示。
不妨换个思路,从forEach入手
基本上大多数支持了闭包的语言,都会对其集合类型list或者array提供一个for循环,更高级一点的,还有一个大家通常称之为forEach的函数式接口。该接口接受一个consumer作为入参:对于集合中的每一个元素,都进行某种特定处理。即
a.forEach(x -> println(x))等价于
for x in a
println(x)现在不妨假设我们有[1,2,3,4]这样一个列表,使用forEach挨个打印它们将会打印出4行,分别是1,2,3,4。如果我们想打印成2,3,4,5,或者说,每个元素先分别+1再打印,该如何操作呢?
答案很容易,只需要打印的时候转换一下就行
forEach(i -> println(i + 1));这就够了,以上就是咱这个新式流机制的基本原理。为了更严谨的说明,这里我们需要引入一个流的定义,或者说接口
public interface Seq<T> {
void forEach(Consumer<T> consumer);
}Seq是我对Sequence的简写,意味着序列操作。这里值得注意的是,Java里的Iterable是天然实现了这个接口的。
回到之前的例子。如果我们有一个代表[1,2,3,4]的oldSeq,现在想要得到一个代表[2,3,4,5]的新的newSeq ,根据上述的转换方式,利用Java的匿名类机制,可以很容易实现
Seq<Integer> newSeq = new Seq<Integer>() {
@Override
public void forEach(Consumer<Integer> consumer) {
oldSeq.forEach(i -> consumer.accept(i + 1));
}
};以上代码的含义为,对于任何一个操作consumer,都是在原有的元素上+1后再操作,这个操作可以是打印,也可以是别的任何行为。进一步的,借用Java 8的lambda函数,我们可以将其更简洁的写为
Seq<Integer> newSeq = c -> oldSeq.forEach(i -> c.accept(i + 1));至此,聪明的你可能会发现,我们基于平平无奇的forEach接口,推导实现出了第一个具有里程碑意义的函数式接口,那就是伟大的map! 于是我们有了
public interface Seq<T> {
void forEach(Consumer<T> consumer);
default <E> Seq<E> map(Function<T, E> function) {
return c -> forEach(t -> c.accept(function.apply(t)));
}
}顺理成章,我们还可以依样画葫芦,写出filter的实现
public interface Seq<T> {
void forEach(Consumer<T> consumer);
default <E> Seq<E> map(Function<T, E> function) {
return c -> forEach(t -> c.accept(function.apply(t)));
}
default Seq<T> filter(Predicate<T> predicate) {
return c -> forEach(t -> {
if (predicate.test(t)) {
c.accept(t);
}
});
}
}到这里,我搞出来的这个新的流式API的定义就算讲清楚了。它的后续的一切强大接口和有趣功能,都是基于这样一个简单的forEach 而衍生出来的。
public interface Seq<T> {
void forEach(Consumer<T> consumer);
}这个API是一切的基础,是梦开始的地方。它将带领大家一步步渐入佳境,沿途把橄榄枝抛向几乎所有主流非主流语言,并贯穿整个专栏始终。 |
|