上一篇以及之前的文章我们介绍了Java 8 函数式编程的基本概念以及一些内置的函数式编程接口,那么从这篇文章开始我们来介绍函数式编程接口的使用。
我们通常使用Stream以及Optional两个类,结合函数式编程接口,来进一步简化我们的代码。
首先我们介绍一下Stream类。
Stream 类
Stream可以对多个元素进行一系列的操作,这个词本身的含义是“小溪,河流,一连串,指令流......”的意思,所以我们经常称呼它为“流式操作”。同时它也可以支持对某些操作进行并发处理。
接下来我们看如何创建Stream。
Stream 对象的创建
创建空对象
Stream empty = Stream.empty();
通过集合来创建
List<Integer> list = Lists.newArraylist(2, 1, 4, 3, 6, 5);
Stream listStream = list.stream();
Stream parallelListStream = list.parallelStream();
上面创建的第一个Stream对象是串行的对象,第二个是并行的Stream对象。
通过of 来创建
Stream stringStream = Stream.of("a", "b", "c", "d");
通过stream 的generate 方法来创建
通过generate 方法可以生成一个无需队列,每个元素都可以进行随机地生成。一般用这个方法生成常量的Stream 和一些随机的Stream 等等。
generate 方法中需要一个Supplier 函数式编程接口的实现作为参数,看一下例子,一看就明白:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
这个写法看不懂没关系,我们可以给出一个比较好理解的写法:
Stream.generate(new Supplier<Object>() {
@Override
public Object get() {
return Math.random();
}
}).limit(5).forEach(ele -> System.out.println(ele));
上述示例完成的功能就是随机生成5 个double 数字并输出。
当然了,还有一些其他创建方法,但是不不怎么常用,在这里就不做过多介绍了。
Stream 的用法
创建完成Stream 对象之后,我们就要学习如何使用了。Stream 对象提供了很多个很实用的方法,这些方法总结以后可以分成两类进行记忆。
- 中间操作:将一个
Stream进行转换,生成另外一个Stream。可能是消费元素,生成新类型的Stream,如map操作;也可能是对Stream中的元素进行过滤,如filter操作。 - 结束操作:不是对
Stream转换,而是产生一个结果或者进行其它的复合操作。如findFirst,toArray等。
接下来我们挑一些常用的方法进行讲解。首先我们介绍中间操作。
Stream 对象常用的中间操作方法
map 方法
map 有“映射”的意思,这个方法的作用就是对Stream 中的元素作一对一转换。
它接收一个Function 接口的实例作为参数,对Stream中的每个元素进行处理。返回的Stream 对象中的元素就是经过传入的Function 接口实例处理完成之后的结果。
其方法定义如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
这里举个简单的例子,我们要将一个Integer 类型的Stream 转换成Double 类型的Stream,代码如下:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Double> streamDouble = stream.map(Integer::doubleValue);
flatmap 方法
这个方法可能对于初学者来说,比较难于理解,因为网上有各种比较绕的教程,实在让人捉摸不透。
其实这也是一个比较常用的方法,flat 的意思有“水平地、平坦地、完全地、彻底地” 这些意思。这个方法可以理解为 “摊平”映射。
“摊平”映射 怎么理解呢,其实它就是对元素进行一对多的转换,这里有两个操作。
- 对原来的
Stream中的所有元素,使用Function参数进行处理,每个元素经过处理后,会生成一个多个元素的Stream对象 - 然后将返回的所有
Stream对象中的所有元素组合成一个统一的Sream并返回
上述两步操作就相当于把原先的Stream 中的所有元素“掰开了,揉碎了,再捏到一起”。
方法定义如下:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
举例我们要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母然后输出,代码如下:
Stream<String> stream = Stream.of("abcdefg", "h", "ijkl", "mno");
stream.flatMap(e -> Arrays.stream(e.split(""))).forEach(System.out::println);
filter 方法
filter 方法的作用就很明显了,是用来“过滤”Stream 对象中的元素的,然后返回一个过滤后的Stream
其方法定义如下:
Stream<T> filter(Predicate<? super T> predicate);
filter 中的参数为Predicate 类型,判断Stream 中元素符合条件(结果为true)的留下,其他的会被过滤掉。
这里假设一个场景,要过滤掉1 到10 中所有的偶数,只留奇数,然后输出,代码如下:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
stream.filter(ele -> ele % 2 != 0).forEach(System.out::println);
distinct 方法
distinct 方法的作用就是去重,对于Stream 对象中的元素作去重处理,返回一个没有重复元素的Stream。
这个方法也比较好理解,同时也比较常用,举例如下:
Stream<String> stream = Stream.of("abc", "ab", "cc", "abc", "ac", "cc");
stream.distinct().forEach(System.out::println);
这样就会把这个String 类型的Stream 中的重复的字符串都过滤掉,最后只剩abc/ab/cc/ac。
sorted 方法
sorted 方法一般来说,常用的就这两种使用方式:
- 直接使用无参的
sorted()函数,默认使用自然序排序,前提是Stream中的的元素必须实现Comparable接口 - 使用含有
Comparator参数的sorted(Comparator<? super T> comparator)函数,可以按照升序或着降序来排序元素。当然我们可以使用lambada表达式来创建一个Comparator实例,我们看下面的示例。
假设我们有一个Student 类的list,现在我们做如下操作:
// 以自然序对list 进行排序
list.stream().sorted()
// 对list 按照自然序逆序排列,通过Comparator 提供的reverseOrder() 方法
list.stream().sorted(Comparator.reverseOrder())
// 使用传入的Comparator 参数来排序这个list
list.stream().sorted(Comparator.comparing(Student::getAge))
// 根据传入的Comparator 参数来对list 进行排序,然后使用Comparator 的reverseOrder() 方法将元素进行逆序排列
list.stream().sorted(Comparator.comparing(Student::getAge).reversed())
Stream 对象常用的结束操作方法
forEach 方法
forEach 方法会对Stream 中的所有元素进行迭代处理,无返回值,它的参数是一个Consumer,方法定义如下:
void forEach(Consumer<? super T> action);
这里举例,遍历一个list,代码如下:
Stream stream = Stream.of(1, 2, 3, 5);
stream.forEach(System.out::println);
forEach 方法用的比较多,相信读者也比较熟悉了,这里就仅仅做个回顾。
min/max/count 方法
min 方法的作用就是返回所有元素中最小值的Optional 对象,如果Stream 中没有元素存在,那么将会返回Optional.Empty(); max 方法与min 相反,就是返回Stream 中最大的,其他特性和min 一致。
相信读者已经注意到了,这两个方法返回的是一个Optional 对象,而不是Stream 中的元素的类型。
count 方法的作用就是所有元素个数。
由于这三个方法比较常用且简单,这里就不举例子了。
anyMatch/allMatch/noneMatch 方法
这两个方法从名字我们大致就可以看出它们的作用了。
anyMatch 方法的作用是判断只要Stream 中有一个元素满足传入的Predicate 的条件时就返回True,否则返回False。
allMatch 方法的作用是要判断所有元素是否都满足传入的Predicate 的条件,都满足时返回True,否则返回False。
noneMatch 方法就是判断所有元素是否均不满足传入的Predicate 条件,如果均不满足,返回True,否则返回False
findFirst/findAny 方法
这两个方法也是看名字就比较容易知道它们的作用,但是也有一些需要注意的地方。
findFirst 返回Stream 中第一个元素的Optioanl 对象;如果Stream 中无元素,那么返回的是Optional.empty(); 如果Stream是无序的(比如对Stream 做并行操作),那么任何元素都可能被返回。
findAny 返回Stream 中任意一个元素的Optional 对象,如果Stream 中没有元素,那么返回空的Optioanl 对象。
总结
这篇文章我们主要介绍了Stream 的结束操作的一些方法,后续文章我们继续介绍Stream 结束操作的另外几个方法。