【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(中)

简介: 【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(中)

   映射系列


方法 | 描述


| :-: | -: map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

mapToDouble(ToDoubleFunction f)| 同上

mapToInt(ToIntFunction f)| 同上

mapToLong(ToLongFunction f)| 同上

flatMap(Function f)| 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流


  public static void main(String[] args) {
        final long count = 10;
        List<Long> list = new ArrayList<>();
        for (long i = 0; i < count; i++) {
            list.add(i);
        }
        //使用mapToLong来处理
        list.stream().mapToLong(x -> x + 10).forEach(System.out::println);
    }


普通的Map映射相对来说比较简单,因此这里也先一笔带过了。现在重点讲解一下flatMap的使用和场景:


//给定一个需求:给定一个列表{"aaa","bbb","ddd","eee","ccc"}。需要在控制台直接输出aaabbbdddeeeccc字样

看如下代码实现,对比下map和flatMap的区别


 public static void main(String[] args) {
        List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc");
        //采用map来做(这里采用了两次forEach循环进行输出,显然不太优雅)
        list.stream().map(x -> {
            List<Character> characterList = new ArrayList<>();
            char[] chars = x.toCharArray();
            for (char c : chars) {
                characterList.add(c);
            }
            return characterList.stream();
        }).forEach(xStream -> xStream.forEach(System.out::print)); //aaabbbdddeeeccc
        //采用flatMap来做  体会一下flatMap的魅力吧
        list.stream().flatMap(x -> {
            List<Character> characterList = new ArrayList<>();
            char[] chars = x.toCharArray();
            for (char c : chars) {
                characterList.add(c);
            }
            return characterList.stream();
        }).forEach(System.out::print); //aaabbbdddeeeccc
    }


再看一个例子


//给定一个需求:给定单词列表["Hello","World"],要返回列表["H","e","l", "o","W","r","d"]


对于这样的需求,我们可能想到的第一个版本可能是这样子的:

  public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world");
        List<String[]> collect = list.stream().map(word -> word.split(""))
                .distinct()
                .collect(Collectors.toList());
    }


不用输出结果,一看返回值的结构就肯定不是我们想要的结果.


这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列表)。因此, map 返回的流实际上是Stream<String[]> 类型的。你真正想要的是用Stream来表示一个字符流。因此,这是行不通的。

正确的姿势:


 public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world");
        list.stream().flatMap(x -> Arrays.stream(x.split("")))
                .distinct().forEach(System.out::print); //helowrd
    }


其实map和flatMap的差别特别像List的add方法和addAll方法的差异,可参照理解一下,看下面这个例子


  public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        List list1 = new ArrayList();
        list1.add(3);
        list1.add(4);
        //注意add和addAll输出的区别
        //list.add(list1);
        //System.out.println(list); //[1, 2, [3, 4]]
        list.addAll(list1);
        System.out.println(list); //[1, 2, 3, 4]
    }
  • 排序
    方法 | 描述


  • | :-: | -:
    sorted() | 产生一个新流,其中按自然顺序排序
    sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序
    这个比较简单,这里就不举例子了


Stream的终止操作


终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,例如 : List、 Integer,甚至是 void


   查找与匹配

方法 | 描述


   | :-: | -:

allMatch(Predicate p) | 检查是否匹配所有元素

anyMatch(Predicate p) | 检查是否至少匹配一个元素

noneMatch(Predicate p) | 检查是否没有匹配所有元素

findFirst() | 返回第一个元素

findAny() | 返回当前流中的任意元素

count() | 返回流中元素总数

max(Comparator c) | 返回流中最大值

min(Comparator c) | 返回流中最小值

forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, Stream API 使用内部迭代)

forEachOrdered(Consumer c) | 基本同forEach,后面会有示例比较

toArray() toArray(IntFunction g) | 这个使用起来和List的toArray差不多

其余方法使用起来都比较简单,下面通过一个案例对比foreach等:


 public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
        //因为并行 所以输出完全无序
        list.stream().parallel().forEach(x -> System.out.print(x)); //65387421
        System.out.println();
        //使用了forEachOrdered 所以是顺序输出的 即使你是并行流也是顺序的
        list.stream().parallel().forEachOrdered(x -> System.out.print(x)); //12345678
        System.out.println();
        //使用了toList,然后其实也是顺序输出了  内部原理同forEachOrdered(可当面试题哟)
        List<Integer> collect = list.stream().parallel().collect(Collectors.toList());
        System.out.println(collect); //[1, 2, 3, 4, 5, 6, 7, 8]
    }


除了使用forEachOrdered保证顺序外,Collectors.toList()也可以保证顺序,二都最终都是通过ForEachOrderedTask类来实现的,具体可以参看ForEachOp.ForEachOrderedTask类中的代码。


注 : 流进行了终止操作后,不能再次使用


归约:


image.png


reduce是很重要的一种变成思想。这里重点介绍一下。reduce的作用是把stream中的元素给组合起来。至于怎么组合起来:


    它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终产出,这就是reduce的算法最通俗的描述;


所以运用reduce我们可以做sum,min,max,average,所以这些我们称之为针对具体应用场景的reduce,这些常用的reduce,stream api已经为我们封装了对应的方法。


//求和 sum
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        // 没有起始值时返回为Optional类型
        Optional<Integer> sumOptional = integers.stream().reduce(Integer::sum);
        System.out.println(sumOptional.get()); //15
        // 可以给一个起始种子值
        Integer sumReduce = integers.stream().reduce(0, Integer::sum);
        System.out.println(sumReduce); //15
        //直接用sum方法
        Integer sum = integers.stream().mapToInt(i -> i).sum();
        System.out.println(sum); //15

重点说说三个参数的Reduce


三个参数时是最难以理解的。 分析下它的三个参数:


   identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等;后面会说到这些用法。


   accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的


   combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作

第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。

因此针对这个方法的分析需要分并行与非并行两个场景。


就是因为U和T不一样,所以给了我们更多的发挥。比如设U的类型是ArrayList,那么可以将Stream中所有元素添加到ArrayList中再返回了,如下示例:


public static void main(String[] args) {
        ArrayList<String> result = Stream.of("aa", "ab", "c", "ad").reduce(new ArrayList<>(),
                (u, s) -> {
                    u.add(s);
                    return u;
                }, (strings, strings2) -> strings);
        System.out.println(result); //[aa, ab, c, ad]
    }

注意由于是非并行的,第三个参数实际上没有什么意义,可以指定r1或者r2为其返回值,甚至可以指定null为返回值。下面看看并行的情况:


当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。注意由于采用了并行计算,前两个参数与非并行时也有了差异! 看个例子:


  public static void main(String[] args) {
        Integer reduce = Stream.of(1, 2, 3).parallel().reduce(
                 4,
                (integer, integer2) -> integer + integer2,
                (integer, integer2) -> integer + integer2);
        System.out.println(reduce); //18
    }
输出:18


omg,结果竟然是18。显然串行的话结果是10;这个不太好理解,但是我下面写一个等价的方式,可以帮助很好的理解这个结果:


 public static void main(String[] args) {
        Optional<Integer> reduce = Stream.of(1, 2, 3).map(n -> n + 4).reduce((s1, s2) -> s1 + s2);
        System.out.println(reduce.get()); //18
    }


这种方式有助于理解并行三个参数时的场景,实际上就是第一步使用accumulator进行转换(它的两个输入参数一个是identity, 一个是序列中的每一个元素),由N个元素得到N个结果;第二步是使用combiner对第一步的N个结果做汇总。


好了,三个参数的reduce先介绍到这。下面继续看看reduce能为我们做什么?


public static void main(String[] args) {
        //构造字符串流
        List<String> strs = Arrays.asList("H", "E", "L", "L", "O");
        // reduce
        String concatReduce = strs.stream().reduce("", String::concat);
        System.out.println(concatReduce); //HELLO
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        Integer minReduce = integerStream.reduce(Integer.MAX_VALUE, Integer::min);
        System.out.println(minReduce); //1
    }


   收集(collect),很多时候和reduce很像,但collect更加强大

方法 | 描述


   | :-: | -:

collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法


Collectors里常用搜集器介绍:

image.png


image.png

相关文章
|
11月前
|
Java API 数据处理
Java新特性:使用Stream API重构你的数据处理
Java新特性:使用Stream API重构你的数据处理
|
11月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
461 100
|
11月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
431 101
|
11月前
|
并行计算 Java 大数据
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
534 101
|
存储 Java API
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
499 188
|
存储 Java API
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
414 92
|
10月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
386 1
|
11月前
|
存储 数据可视化 Java
Java Stream API 的强大功能
Java Stream API 是 Java 8 引入的重要特性,它改变了集合数据的处理方式。通过声明式语法,开发者可以更简洁地进行过滤、映射、聚合等操作。Stream API 支持惰性求值和并行处理,提升了代码效率和可读性,是现代 Java 开发不可或缺的工具。
231 0
Java Stream API 的强大功能
|
11月前
|
人工智能 算法 搜索推荐
拼多多:通过用户分组API实施差异化营销策略,提高客单价
拼多多通过用户分组API实现差异化营销,精准提升客单价。基于用户行为数据自动分类,针对不同群体推送专属优惠,如高频用户推高端商品、新用户送礼包、低频用户唤醒激励。结合满减、捆绑销售等策略,有效提高单笔订单金额。该策略提升营销效率,增强用户粘性,助力平台实现数据驱动的可持续增长。
|
存储 NoSQL Java
Java Stream API:集合操作与并行处理
Stream API 是 Java 8 提供的集合处理工具,通过声明式编程简化数据操作。它支持链式调用、延迟执行和并行处理,能够高效实现过滤、转换、聚合等操作,提升代码可读性和性能。

热门文章

最新文章