Java 8 函数式编程(Stream 的创建及一些简单接口使用)

182 阅读7分钟

上一篇以及之前的文章我们介绍了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 对象提供了很多个很实用的方法,这些方法总结以后可以分成两类进行记忆。

  1. 中间操作:将一个Stream 进行转换,生成另外一个Stream。可能是消费元素,生成新类型的Stream,如map 操作;也可能是对Stream 中的元素进行过滤,如filter 操作。
  2. 结束操作:不是对Stream 转换,而是产生一个结果或者进行其它的复合操作。如findFirsttoArray等。

接下来我们挑一些常用的方法进行讲解。首先我们介绍中间操作

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 的意思有“水平地、平坦地、完全地、彻底地” 这些意思。这个方法可以理解为 “摊平”映射

“摊平”映射 怎么理解呢,其实它就是对元素进行一对多的转换,这里有两个操作。

  1. 对原来的Stream 中的所有元素,使用Function 参数进行处理,每个元素经过处理后,会生成一个多个元素的Stream 对象
  2. 然后将返回的所有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)的留下,其他的会被过滤掉。

这里假设一个场景,要过滤掉110 中所有的偶数,只留奇数,然后输出,代码如下:

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 方法一般来说,常用的就这两种使用方式:

  1. 直接使用无参的sorted() 函数,默认使用自然序排序,前提是Stream 中的的元素必须实现Comparable 接口
  2. 使用含有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 结束操作的另外几个方法。