Java 8-Stream API-流操作

java.util.stream.Stream中的Stream接口定义了许多操作。它们可以分为两大类。

  • filter、map和limit可以连成一条流水线
  • collect触发流水线执行并关闭它

可以连接起来的操作称为中间操作,关闭流的操作称为终端操作。

操作 类型 返回类型 使用的类型/函数式接口 函数描述符
filter 中间 Stream<T> Predicate<T> T->boolean
distinct 中间
(有状态-无界)
Stream<T>
skip 中间
(有状态-有界)
Stream<T> long
limit 中间
(有状态-有界)
Stream<T> long
map 中间 Stream<R> Function T->R
flatMap 中间 Stream<R> Function<T,Stream<R>> T->Stream<R>
sorted 中间
(有状态-无界)
Stream<T> Comparator<T> (T,T)->int
anyMatch 终端 boolean Predicate<T> T->boolean
noneMatch 终端 boolean Predicate<T> T->boolean
allMatch 终端 boolean Predicate<T> T->boolean
findAny 终端 Optional<T>
findFirst 终端 Optional<T>
forEach 终端 void Consumer<T> T->void
collect 终端 R Collector<T,A,R>
reduce 终端
(有状态-有界)
Optional<T> BinaryOperator<T> (T,T)->T
count 终端 long

中间操作

诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理—他们很懒。

筛选与切片

filter方法,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。

distinct方法,返回一个元素各异(根据所生成元素的hashCode和equals方法实现)的流。

limit(n)方法,该方法会返回一个不超过给定长度的流。

skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。

查找和匹配

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。anyMatch方法返回一个boolean,因此是一个终端操作。

boolean b=menu.stream().anyMatch(Dish::isVegetarian)

allMatch方法检查谓词是否匹配所有元素

noneMatch如果没有匹配的返回true,有一个匹配就返回false

anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路。

findAny方法将返回当前流中的任意元素。

Optional<Dish> dish=menu.stream()
                .filter(Dish::isVegetarian)
                .findAny();

Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面代码中,findAny可能什么元素都没有找到。

Optional中的方法

  • isPresent():Optional包含值的时候返回true,否则返回false
  • ifPresent(Consumer block):会在值存在的时候执行给定的代码块,不存在就什么都不做
  • T get()会在值存在时返回值,否则抛出一个NoSuchElement异常
  • T orElse(T other)会在值存在时返回值,否则返回一个默认值
menu.stream()
    .filter(Dish::isVegetarian)
    .findAny()
    .ifPresent(d->System.out.println(d.getName));

查找第一个元素

findFirst找到第一个元素

关于findAny和findFirst。找到第一个元素在并行上限制更多,如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。

归约

将流中所有元素反复结合起来,得到一个值。这样的查询可以被归类为归约操作(将流归约成一个值)。

元素求和

之前的方式

int sum=0;
for(int x:numbers){
    sum+=x;
}

这段代码中有两个参数

  • 总和变量的初始值,在这里是0
  • 将列表中所有元素结合在一起的操作,在这里是+
int sum=numbers.stream().reduce(0,(a,b)->a+b);
int sum=numbers.stream().reduce(0,Integer::sum);

reduce接收两个参数:

  • 一个初始值,这里是0
  • 一个BinaryOperator来将两个元素结合起来产生一个新值。
int product=numbers.stream().reduce(1,(a,b)-a*b);

Lambda反复结合每个元素,直到流被归约成一个值。

比如,0,5,3,9

首先,0作为Lambda(a)的第一个参数,从流中获得4作为第二个参数(b)。0+4得到4,它成了新的累积值。然后在用累积值和流中下一个元素5调用Lambda,产生新的累积值9.接下来,再用累积值和下一个元素3调用Lambda,得到12.最后,用12和流中最后一个元素9调用Lambda,得到最终结果21.

无初始值

Optional<Integer> sum=numbers.stream().reduce((a,b)->a+b);

最大值和最小值

reduce接受两个参数

  • 一个初始值
  • 一个Lambda把两个流元素结合起来并产生一个新值
Optional<Integer> max=numbers.stream().reduce(Integer::max);
Optional<Integer> max=numbers.stream().reduce((x,y)->x<y?x:y);

怎样用map和reduce方法数一数流中有多少个菜呢?

答案:可以把流中每个元素都映射成数字1,然后用reduce求和。这相当于按顺序数流中的元素个数。

int count=menu.stream()
        .map(d->1)
        .reduce(0,(a,b)->a+b);

map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名,因为它很容易并行化。

归约方法的优势与并行化

使用reduce的好处在于,这里的迭代被内部迭代抽象掉了,这让内部实现得以选择并行执行reduce操作。而迭代求和例子要更新共享变量sum,这不是那么容易并行化的。如果你加入了同步,很可能会发现线程竞争抵消了并行本应带来的性能提升!这种计算的并行化需要另一种办法:将输入分块,分块求和,最后再合并起来。但这样的话代码看起来就完全不一样了。可变的累加器模式对于并行化来说是死路一条。你需要一种新的模式,这正是reduce所提供的。使用流来对所有的元素并行求和时,你的代码几乎不用修改:stream()换成了parallelStream()

int sum=numbers.parallelStream().reduce(0,Integer::sum);

但要并行执行这段代码也要付出一定代价,传递给reduce的Lambda不能更改状态(如实例变量),而且操作必须满足结合律才可以按任意顺序执行。

流操作:无状态和有状态

诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内部可变状态)。

但诸如reduce、sum、max等操作需要内部状态来积累。在上面的情况下,内部状态很小。在我们的例子里就是一个int或double。不管流中有多少元素要处理,内部状态都是有界的。

相反,诸如sort或distinct等操作一开始都和filter和map差不多—都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限流的,就可能会有问题(把质数流倒序会做什么呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。

映射

可以使用map和flatMap提取或转换流中的元素
Arrays.stream()方法可以接受一个数组并产生一个流

flatMap可以将各个生成流扁平化为单个流

List<String> uniqueCharacters=
    words.stream()
        .map(w->w.split(""))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());

flatMap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

需求:给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1,2,3]和列表[3,4],应该返回[(1,3),(1,4),(2,3),(2,4),(3,3),(3,4)]

List<Integer> numbers1=Arrays.asList(1,2,3);
List<Integer> numbers2=Arrays.asList(3,4);
List<int[]> pairs=
    numbers1.stream()
    .flatMap(i->numbers2.stream()
             .map(j->new int[]{i,j}))
    .collect(toList());

猜你喜欢

转载自blog.csdn.net/zsx157326/article/details/80886633