Java8中stream和functional interface的配合使用详解
前言
java 8 提供了一组称为 stream 的 api,用于处理可遍历的流式数据。stream api 的设计,充分融合了函数式编程的理念,极大简化了代码量。
大家其实可以把stream当成一个高级版本的iterator。原始版本的iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给stream就好了!(这个秘籍,一般人我不告诉他:))
我们来讲解如何将常用的 stream api 与相应的 functional interface (函数式接口)配合使用,达成数据处理的目的。
类似于困扰哲学家们数千年的三大问题,关于 stream 我们也有三个疑团需要解开:它从哪里来?它能做什么?它会变成什么?
generate 与 supplier
stream 最常见的来源是 collection。collection 是一组可遍历元素的抽象容器。它有两大类实现:不允许重复元素的 set 和允许重复的 list。只要在某个 collection 对象后面加上 .stream() 或者 .parallelstream() 就可以得到相应的 stream 了。
如果没有现成的 collection,或者 collection 太大根本存不下,还有什么办法可以生成 stream 么?如果知道生成 stream 中每个元素的算法,就可以无中生有造出一个 stream 来。这里用到的是方法 stream.generate(),它依赖于一个函数式接口 supplier。
static <t> stream<t> generate(supplier<t> s);
supplier 的方法 get() 在每次调用时都返回一个 t 的对象。因为 get() 方法不接收任何参数,所以使用 generate 时,代码总是会写成类似 () -> returnvalue 的样子。
另外,由于 get() 可以被调用无限多次,因此通过 generate 生成的 stream 也是无限长的,必要时可以通过 .limit() 截取前若干个元素。
例如,如果想获得一个无限长的随机 uuid 序列,可以使用下面的方法:
stream<uuid> infiniteuuidstream = stream.generate(() -> uuid.randomuuid());
想要获取诸如 1 ~ 10 这样的序列也是可行的,但需要一个 helper class 记录当前状态,这里就不提供案例了。
foreach 与 consumer
知道了如何生成 stream,也要知道如何消费它。既然 stream 可以从 collection 来,那么最后应该也能变成 collection,这就是 collect() 的功劳了。collect() 接收一个 collector 作为参数,返回从 stream 生成的 collection 对象。不过这个 collector 不是函数式接口,所以不属于本文的重点。下面着重讲解的是 foreach 方法。
void foreach(consumer<? super t> action);
foreach 与函数式接口 consumer 配合工作,consumer 的 void accept(t t) 方法就是来消费 stream 中的各个元素的。因为 accept 接收单个元素 t 作为参数,foreach 会写成 e -> statement 的形式,其中 statement 不返回任何值。
比如,逐行打印 stream 中的每一个元素,就可以写作:
stream.foreach(e -> system.out.println(e));
或者通过方法引用进一步简化:
stream.foreach(system.out::println);
reduce 与 binaryoperator
除了 foreach 这种吞噬元素的终结型操作以外,使用 stream 中的元素还有两种常见的模式。第一种依旧是终结型操作:整合所有的元素,最后返回一个单一的值,我们把这个操作称作 reduce。第二种则是过程性操作,它让每个元素都有自己对应的返回值,之后重组成为新的 stream,以便下一步继续利用。我们把第二种操作称为 map。把刚刚提及的这两个操作结合起来,就是大名鼎鼎的 mapreduce 了(误)。
reduce 与一种特殊的函数式接口搭配使用,它叫 binaryoperator。binaryoperator<t> 的原型是 bifunction<t, t, t>,那这个 bifunction 又是怎么回事呢?原来,bifunction<t, u, r> 是一个宽泛的函数式接口,它的方法 r apply(t t, u u) 接受类型为 t 和 u 的两个参数,并返回一个类型为 r 的值。如果 t u r 这三者的类型相同,就可以写作 bifunction<t, t, t>。因为这种用法尤其常见,于是它有了自己专属的名字,即 binaryoperator<t>。最常见的 binaryoperator 当属二元算术操作,我们熟知的加减乘除都属于这个范畴。
讲解 reduce 时最常见的例子就是求一个 stream 中所有元素之和了:
// stream: stream<integer> optional<integer> sum = stream.reduce((a, b) -> a + b);
我们可以看出,reduce 方法的特征是 (a, b) -> returnvalue。它返回的结果是 optional,我们可以用 .ispresent() 查看是否为空值;当值不为空时,用 .get() 获取数据。
map 与 function
map 或许是 stream 中使用最为广泛的一个操作了。与 reduce 涉及的 bifunction 不同,与 map 配套使用的函数式接口是略为简单的 function。它同样是一个宽泛的函数式接口,同时也是函数式接口最著名的代表。function<t, r> 的方法 r apply(t t) 接受一个类型为 t 的参数,并返回一个类型为 r 的值。map 所做的事情,就是把这个 function 应用于 stream 中的每一个元素,以得到一个新的全部由 r 组成的 stream。
比如说,把一个 stream 中的每一个字符串都变成大写:
// original: stream<string> stream<string> transformed = original.map(e -> e.touppercase());
map 方法的特征是 e -> returnvalue。正如我们之前用过的 system.out::println 一样,这里也可以使用方法引用简化代码,只要引用的方法符合 map 预期的类型即可:传入一个 t 参数,返回一个 r 值。
// original: stream<string> stream<string> transformed = original.map(string::touppercase);
filter 与 predicate
介绍了 foreach,reduce 和 map 这些重量级的操作,下面我们来处理一个尴尬的问题:如果这个 stream 中有我们不想要的元素怎么办?答案是使用 filter 把他们踢出去。
与 filter 搭配使用的函数式接口是 predicate。predicate<t> 的方法 boolean test(t t) 接受一个类型为 t 的参数,并返回 true 或是 false。我们可以认为 predicate<t> 就是特异化的 function<t, boolean>,因为它使用得足够广泛,所以自立门户成为一套单独的接口。
在下面的例子中,程序只打印 stream 中的偶数:
// stream: stream<integer> stream.filter(e -> e % 2 == 0).foreach(system.out::println);
可以看出,由于 predicate 是一种特异的 function,所以 filter 方法的特征与 map 在外观上如出一辙。不过 filter 要保证 e -> returnvalue 中的 returnvalue 是一个 boolean,否则编译会报错。
sorted 与 comparator
最后来看看 stream 中非常强大的 sorted 方法,它允许我们自定义比较规则对 stream 中的元素排序。与 sorted 搭配的函数式接口是 comparator,comparator<t> 使用 int compare(t o1, t o2) 方法比较两个 t 类型的对象。排序正是通过比较对象之间的相对大小实现的。
接下来的例子将 stream 中的浮点数按绝对值的升序排列,并打印出来:
// stream: stream<double> stream.sorted((a, b) -> { double diff = a - b; if (diff < 0) return -1; else if (diff > 0) return 1; else return 0; }).foreach(system.out::println);
不难看出,sorted 方法的特征与 reduce 比较相似,都是 (a, b) -> returnvalue 的结构,但是要保证 returnvalue 是 int 类型。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
推荐阅读
-
Java8中stream和functional interface的配合使用详解
-
Java8中Lambda表达式使用和Stream API详解
-
详解Java8新特性之interface中的static方法和default方法
-
详解Java8新特性之interface中的static方法和default方法
-
Java8中stream和functional interface的配合使用详解
-
Java8中Lambda表达式使用和Stream API详解
-
实例理解Java8新特性中Stream API和Optional类的使用
-
Java8中Lambda和Stream的详解(附代码)
-
Java8中Lambda和Stream的详解(附代码)