【小家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

相关文章
|
4月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
60 3
|
4月前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
105 2
|
2月前
|
存储 Java 开发者
什么是java的Compact Strings特性,什么情况下使用
Java 9引入了紧凑字符串特性,优化了字符串的内存使用。它通过将字符串从UTF-16字符数组改为字节数组存储,根据内容选择更节省内存的编码方式,通常能节省10%至15%的内存。
|
2月前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
101 6
|
3月前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
50 4
|
4月前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
169 3
|
4月前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
40 2
|
5天前
|
API PHP 开发者
速卖通商品详情接口(速卖通API系列)
速卖通(AliExpress)是阿里巴巴旗下的跨境电商平台,提供丰富的商品数据。通过速卖通开放平台(AliExpress Open API),开发者可获取商品详情、订单管理等数据。主要功能包括商品搜索、商品详情、订单管理和数据报告。商品详情接口aliexpress.affiliate.productdetail.get用于获取商品标题、价格、图片等详细信息。开发者需注册账号并创建应用以获取App Key和App Secret,使用PHP等语言调用API。该接口支持多种请求参数和返回字段,方便集成到各类电商应用中。
|
11天前
|
JSON API 数据格式
微店商品列表接口(微店 API 系列)
微店商品列表接口是微店API系列的一部分,帮助开发者获取店铺中的商品信息。首先需注册微店开发者账号并完成实名认证,选择合适的开发工具如PyCharm或VS Code,并确保熟悉HTTP协议和JSON格式。该接口支持GET/POST请求,主要参数包括店铺ID、页码、每页数量和商品状态等。响应数据为JSON格式,包含商品详细信息及状态码。Python示例代码展示了如何调用此接口。应用场景包括商品管理系统集成、数据分析、多平台数据同步及商品展示推广。
|
3天前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
25 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡