Java 8

文章目录

Java 8

注:https://cloud.tencent.com/developer/article/1507721

Stream

1、stream使用
Java8 Stream 使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合进行链状流式的操作。

在这里插入图片描述

如图,Stream中的操作可以分为两大类:中间操作与结束操作,中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算(即惰性求值),这也是Stream在迭代大集合时高效的原因之一。

中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。、

使用:

public static void main(String[] args) {
    List<String> list = Lists.newArrayList(
            "bcd", "cde", "def", "abc");
    List<String> result = list.stream()
            //.parallel()
            .filter(e -> e.length() >= 3)
            .map(e -> e.charAt(0))
            //.peek(System.out :: println)
            //.sorted()
            //.peek(e -> System.out.println("++++" + e))
            .map(e -> String.valueOf(e))
            .collect(Collectors.toList());
    System.out.println("----------------------------");
    System.out.println(result);
}

2、stream原理

流管道
一个流管道 包含一个流来源、0 或多个中间操作,以及一个终止操作。流来源可以是集合、数组、生成器函数或其他任何适当地提供了其元素的访问权的数据源。中间操作将流转换为其他流 — 通过过滤元素 (filter()),转换元素 (map()),排序元素 (sorted()),将流截断为一定大小 (limit()),等等。终止操作包括聚合(reduce()、collect()),搜索 (findFirst()) 和迭代 (forEach())。

关于本系列
借助 java.util.stream 包,您可以简明地、声明性地表达集合、数组和其他数据源上可能的并行批量操作。在 Java 语言架构师 Brian Goetz 编写的这个 系列 中,全面了解 Streams 库并学习如何最充分地使用它。

流管道是惰性构造的。构造流来源不会计算流的元素,而是会确定在必要时如何找到元素。类似地,调用中间操作不会在元素上执行任何计算;只会将另一个操作添加到流描述的末尾。仅在调用终止操作时,管道才会实际执行相应的工作:计算元素,应用中间操作,以及应用终止操作。这种执行方法使得执行多项有趣的优化成为可能。
流来源
流来源有一种称为 Spliterator 的抽象来描述。顾名思义,Spliterator 组合了两种行为:访问来源的元素(迭代),可能分解输入来源来实现并行执行(拆分)。

尽管 Spliterator 包含与 Iterator 相同的基本行为,但它没有扩展 Iterator,而采用了不同的元素访问方法。Iterator 有两个方法:hasNext() 和 next();访问下一个元素可能涉及到(但不需要)调用这两个方法。因此,正确编写 Iterator 需要一定量的防御性和重复性编码。(如果客户端没有在调用 next() 之前调用 hasNext() 会怎么样?如果它调用 hasNext() 两次会怎么样?)此外,这种两方法协议通常需要一定水平的有状态性,比如前窥 (peek ahead ) 一个元素(并跟踪您是否已前窥)。这些要求累积形成了大量的每元素访问开销。

语言中拥有拉姆达表达式使 Spliterator 能够采取一种通常更加高效的元素访问方法 — 而且更容易正确地编码。Spliterator 有两个访问元素的方法:

boolean tryAdvance(Consumer<? super T> action);
void forEachRemaining(Consumer<? super T> action);

tryAdvance() 方法尝试处理单个元素。如果没有元素,tryAdvance() 只会返回 false;否则,它会前移游标,将当前元素传递给所提供的处理函数并返回 true。forEachRemaining() 方法处理所有剩余的元素,将它们一次一个地传递给所提供的处理函数。

即使忽略了并行分解的可能性,Spliterator 抽象也是一个 “更好的迭代器” — 更容易编写,更容易使用,而且通常具有更低的每元素访问开销。但 Spliterator 抽象还扩展到了并行分解领域。一个 spliterator 描述剩余元素的序列,调用 tryAdvance() 或 forEachRemaining() 元素访问方法来在该序列中前进。为了拆分来源,以便两个线程可分别处理输入的不同部分,Spliterator 提供了一个 trySplit() 方法:

Spliterator<T> trySplit();

trySplit() 的行为是尝试将剩余元素拆分为两个部分,这两部分最好具有类似的大小。如果 Spliterator 可以拆分,trySplit() 会将所描述元素的初始部分拆分为一个新 Spliterator,将其返回,并调整其状态,以便描述拆分后的部分后面的元素。如果来源无法拆分,trySplit() 将会返回 null,表明无法拆分且调用方应按顺序继续处理。对于 遇到顺序 很重要的来源(例如数组、List 或 SortedSet),trySplit() 必须保留此顺序;它必须将剩余元素的初始部分拆分到一个新的 Spliterator 中,而且当前 spliterator 必须按照与原始顺序相同的顺序描述剩余元素。

JDK 中的 Collection 实现都已配备了高质量的 Spliterator 实现。允许一些来源获得比其他来源更好的实现:包含多个元素的 ArrayList 始终可以干净且均匀地进行拆分;LinkedList 的拆分效率一直很差;而且基于哈希值和基于树的数据集通常能够进行比较不错的拆分。

构建流管道
流管道是通过构造流来源及其中间操作的链接列表表示来构建的。在内部表示中,管道的每个阶段都通过一个流标志 位图来描述,该位图描述了在流管道的这一阶段已知的元素信息。流使用这些标志优化流的构造和执行。表 1 展示了流标志和它们的解释。

表 1. 流标志
流标志 解释
SIZED 流的大小已知。
DISTINCT 依据用于对象流的 Object.equals() 或用于原语流的 ==,流的元素将有所不同。
SORTED 流的元素按自然顺序排序。
ORDERED 流有一个有意义的遇到顺序(请参阅 “遇到顺序” 部分)。
来源阶段的流标志来自 spliterator 的 characteristics 位图(spliterator 支持比流更大的标志集)。高质量的 spliterator 实现不仅提供了高效的元素访问和拆分,还会描述元素的特征。(例如,一个 HashSet 的 spliterator 报告 DISTINCT 特征,因为已知一个 Set 的元素是不同的。)

“在某些情况下,Streams 可以使用来源和之前的操作的知识来完全省略某个操作。”

每个中间操作都对流标志具有已知的影响;一个操作可设置、清除或保留每个标志的设置。例如,filter() 操作保留 SORTED 和 DISTINCT 标志,但清除 SIZED 标志;map() 操作清除 SORTED 和 DISTINCT 标志,但保留 SIZED 标志;sorted() 操作保留 SIZED 和 DISTINCT 标志,但注入 SORTED 标志。构造阶段的链接列表表示时,会将前一个阶段的标志与当前阶段的行为相组合,以获得当前阶段的一组新标志。

在某些情况下,标志使完全省略一个操作成为可能,就像清单 1 中的流管道一样。

清单 1. 可自动省略操作的流管道

TreeSet<String> ts = ...
String[] sortedAWords = ts.stream()
                          .filter(s -> s.startsWith("a"))
                          .sorted()
                          .toArray();

来源阶段的流标志包含 SORTED,因为来源是一个 TreeSet。filter() 方法保留了 SORTED 标志,所以过滤阶段的流标志也包含 SORTED 标志。通常,sorted() 方法的结果是构造一个新的管道阶段,将它添加到管道末尾,然后返回新阶段。但是,因为已知元素是按自然顺序排序的,所以 sorted() 方法是一个空操作 — 它仅返回前一个阶段(过滤阶段),因为排序是多余的。(类似地,如果元素已知是 DISTINCT,那么可以完全消除 distinct() 操作。)

执行流管道
发起终止操作时,流实现会挑选一个执行计划。中间操作可划分为无状态(filter()、map()、flatMap())和有状态(sorted()、limit()、distinct())操作。无状态操作是可在元素上执行而无需知道其他任何元素的操作。例如,过滤操作只需检查当前元素来确定是包含还是消除它,但排序操作必须查看所有元素之后才知道首先发出哪个元素。

如果管道按顺序执行,或者并行执行,但包含所有无状态操作,那么它可以在一轮中计算。否则,管道会划分为多个部分(在有状态操作边界上划分)并分多轮计算。

终止操作是短路(allMatch()、findFirst())或非短路(reduce()、collect()、forEach())操作。如果终止操作是非短路操作,那么可以批量处理数据(使用来源 spliterator 的 forEachRemaining() 方法,进一步减少访问每个元素的开销);如果它是短路操作,则必须一个元素处理一次(使用 tryAdvance())。

对于顺序执行,Streams 构造了一个 “机器” — 一个 Consumer 对象链,其结构与管道结构相符。其中每个 Consumer 对象知道下一个阶段;当它收到一个元素(或被告知没有更多元素)时,它会将 0 或多个元素发送到链中的下一个阶段。例如,与 filter() 阶段有关联的 Consumer 将过滤器谓词应用于输入元素,并将它发送或不发送到下一个阶段;与 map() 阶段有关联的 Consumer 将映射函数应用于输入元素,并将结果发送到下一个阶段。与有状态操作(比如 sorted())有关联的 Consumer 会缓冲元素,直到它看到输入的末尾,然后将排序的数据发送到下一个阶段。机器中的最后一个阶段将实现终止操作。如果此操作生成了结果,比如 reduce() 或 toArray(),该阶段可充当此结果的累加器。

图 1 显示了以下流管道的 “流机器” 的动画(或者在某些浏览器中显示为快照)。(在图 1 中,黄色、绿色和蓝色块按顺序进入机器顶部的第一个阶段。在第一个阶段,每个块压缩为更小的块,然后进入第二个阶段。在这里,一个类似吃豆人的游戏人物吃掉每个黄色块,仅让绿色和蓝色块落入第三个阶段。压缩的蓝色和绿色块交替显示在计算机屏幕上。)

blocks.stream()
      .map(block -> block.squash())
      .filter(block -> block.getColor() != YELLOW)
      .forEach(block -> block.display());

图 1. 流机器(动画来自 Tagir Valeev)
在这里插入图片描述
3、stream性能

https://www.hollischuang.com/archives/3364

在这里插入图片描述
java8中的map reduce的叫法其实就是借鉴了处理大数据的那个MapReduce。java8中的stream和java I/O中的那个stream有一点略微的不同。

I/O的那个stream更像是一个通道。而java8中的stream指的是在数据流转的过程中还包含有动态处理,就像上图中的一样,从输入然后被map分开,然后分拣合并到reduce,然后输出一个你想要的结果。

java8的stream是一种新的编程模型,它为java处理流数据或者说是处理集合提供了更方便的方式,而不像java8之前那么的笨重。

但是,java8之后就真的没必要用循环了吗?当然不是。stream只适合处理那些顺序next执行的逻辑。如果涉及到游标,则还是要用fori的。

关于性能。之前有人说stream性能较差。通过我们上面的测试对比发现,stream确实要比for循环慢不少。

到底是用循环还是stream,还是要看具体的场合。循环性能好,stream处理集合更加的方便友好。二者看起来都有各自的优势。

也许在对性能要求敏感的场景下,循环可能是个不错的选择。除此之外,可以去尝试下更加方便友好的stream。

Optional

Optional 可以有效的减少If代码的非空判断,Optional和Stream结合可以减少代码中的if else分支和循环遍历,并且可以通过函数式编程精简代码量,减少不必要的错误。同时也可以简化单元测试复杂度。

package com.demo.interviewdemo.java8Demo.optionalDemo;

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * @Description:
 * @Author: xxx
 * @Date: Created in 15:29 2020/11/26
 */
public class OptionalDemo<T> {

    public static void main(String[] args) {
        List<String> list = Lists.newArrayList();
        list.add("1");
        list.add("2");
        list.add("3");

        //直接遍历全部输出或则处理元素
        if (CollectionUtils.isNotEmpty(list)) {
            for (String s : list) {
                System.out.println(s);
            }
        }

        Optional.ofNullable(list).ifPresent(System.out::print);


        //list 集合转换
        List<Integer> integerList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(list)) {
            for (String s : list) {
                if (StringUtils.isNotBlank(s)) {
                    int i = Integer.valueOf(s);
                    integerList.add(i * 2);
                }

            }
        }

        integerList = Optional.ofNullable(list)
                .map(x -> Lists.transform(x, Integer::valueOf))
                .orElseGet(Lists::newArrayList);


        //查找某个元素 并返回
        Integer result = 0;
        if (CollectionUtils.isNotEmpty(list)) {
            for (String s : list) {
                if ("1".equals(s)) {
                    result = Integer.valueOf(s);
                }
            }
        }


        result = Optional.ofNullable(list)
                .orElseGet(Lists::newArrayList)
                .stream()
                .filter(Predicate.isEqual("1"))
                .map(Integer::valueOf)
                .findFirst()
                .orElseGet(null);

        // 或则

        result = Optional.ofNullable(list)
                .map(OptionalDemo::cal)
                .orElse(null);


        // optional转stream
        // 注 在 java 9 中可以直接Optional to Stream转换  如Optional.ofNullable(list).stream(),这样对集合的操作就更加方便。

        result = Optional.ofNullable(list).map(List::stream)
                .orElseGet(Stream::empty)
                .filter(Predicate.isEqual("1"))
                .map(Integer::valueOf)
                .findFirst()
                .orElse(null);


        List<String> list1 = null;

        OptionalDemo.method(list1);




    }

    private static void method(List<String> list1) {
        // 注 这种直接get很危险 容易报错要通过map 进行转换处理 或则orElseGet
        Optional.ofNullable(list1).get()
                .stream()
                .findFirst();
    }

    //或则在map转换中抽象出来方法 如果为空不会走map 所以不用加if非空判断
    private static Integer cal(List<String> list) {
        return list.stream()
                .filter(Predicate.isEqual("1"))
                .map(Integer::valueOf)
                .findFirst()
                .orElseGet(null);
    }


}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值