Java8新特性之Stream流(高级篇)(上)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

前言


文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…

种一棵树最好的时间是十年前,其次是现在

絮叨


今天 开始写Java8新特性系列,怎么说呢,主要有几个新东西

  • Lambda表达式
  • 函数式接口
  • 方法引用
  • Stream流
  • Optional类
  • default关键字

这个四个的主要作用 简化代码编写,提高性能等等,但是也会给维护带来麻烦,因为不懂的人去看,真心累,但是写起来是真的香,今天打算讲标题上的。今天讲讲我们这个Stream流,前面几节可以参考下面链接


🔥你不知道的Java内部类

🔥你必须知道的Java泛型

🔥Java8新特性之Lambda表达式,函数式接口,方法引用和default关键字

🔥Java8新特性之Optional类

🔥Java8新特性之Stream流(基础篇)

昨天的是入门,今天跟大家讲讲点高级东西哈


Stream的Collector


介绍

前面我们使用过collect(toList()),在流中生成列表。实际开发过程中,List又是我们经常用到的数据结构,但是有时候我们也希望Stream能够转换生成其他的值,比如Map或者set,甚至希望定制生成想要的数据结构。

collect也就是收集器,是Stream一种通用的、从流生成复杂值的结构。只要将它传给collect方法,也就是所谓的转换方法,其就会生成想要的数据结构。这里不得不提下,Collectors这个工具库,在该库中封装了相应的转换方法。当然,Collectors工具库仅仅封装了常用的一些情景,如果有特殊需求,那就要自定义了。

显然,List是能想到的从流中生成的最自然的数据结构, 但是有时人们还希望从流生成其他值, 比如 Map 或 Set, 或者你希望定制一个类将你想要的东西抽象出来。

是收集器,一种通用的、从流生成复杂值的结构。只要将它传给collect 方法,所有的流就都可以使用它了


Collector(最常用的)


Collector是Stream的可变减少操作接口,可变减少操作包括:将元素累积到集合中,使用StringBuilder连接字符串;计算元素相关的统计信息,例如sum,min,max或average等。Collectors(类收集器)提供了许多常见的可变减少操作的实现。

首先我们来看看它长什么样


Collector<T, A, R>接受三个泛型参数,对可变减少操作的数据类型作相应限制:

  • T:输入元素类型
  • A:缩减操作的可变累积类型(通常隐藏为实现细节)
  • R:可变减少操作的结果类型

Collector接口声明了4个函数,这四个函数一起协调执行以将元素目累积到可变结果容器中,并且可以选择地对结果进行最终的变换


太多了,哈哈 我们慢慢来看


转换成其他集合


对于前面提到了很多Stream的链式操作,但是,我们总是要将Strea生成一个集合,比如:

  • 已有代码是为集合编写的, 因此需要将流转换成集合传入;
  • 在集合上进行一系列链式操作后, 最终希望生成一个值;
  • 写单元测试时, 需要对某个具体的集合做断言。

有些Stream可以转成集合,比如前面提到toList,生成了java.util.List 类的实例。当然了,还有还有toSet和toCollection,分别生成 Set和Collection 类的实例。这个是最常用的用法之一

toList

List<Integer> collectList = Stream.of(1, 2, 3, 4)
        .collect(Collectors.toList());
System.out.println("collectList: " + collectList);
// collectList: [1, 2, 3, 4]
复制代码


toSet

Set<Integer> collectSet = Stream.of(1, 2, 3, 4)
        .collect(Collectors.toSet());
System.out.println("collectSet: " + collectSet);
// collectSet: [1, 2, 3, 4]
复制代码


toMap

如果生成一个Map,我们需要调用toMap方法。由于Map中有Key和Value这两个值,故该方法与toSet、toList等的处理方式是不一样的。toMap最少应接受两个参数,一个用来生成key,另外一个用来生成value。toMap方法有三种变形:

Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName));  
复制代码



转成值

使用collect可以将Stream转换成值。maxBy和minBy允许用户按照某个特定的顺序生成一个值。

  • averagingDouble:求平均值,Stream的元素类型为double
  • averagingInt:求平均值,Stream的元素类型为int
  • averagingLong:求平均值,Stream的元素类型为long
  • counting:Stream的元素个数
  • maxBy:在指定条件下的,Stream的最大元素
  • minBy:在指定条件下的,Stream的最小元素
  • reducing: reduce操作
  • summarizingDouble:统计Stream的数据(double)状态,其中包括count,min,max,sum和平均。
  • summarizingInt:统计Stream的数据(int)状态,其中包括count,min,max,sum和平均。
  • summarizingLong:统计Stream的数据(long)状态,其中包括count,min,max,sum和平均。
  • summingDouble:求和,Stream的元素类型为double
  • summingInt:求和,Stream的元素类型为int
  • summingLong:求和,Stream的元素类型为long


举个例子(简单的大家自己去试):

public static void main(String[] args) {
        List<String> strings = Arrays.asList("六脉神剑", "大神", "小菜鸡", "交流群:549684836");
        Integer integer = strings.stream().filter(string -> string.length() <= 6).map(String::length).sorted().limit(2)
                .distinct().collect(Collectors.maxBy(Comparator.comparing(Integer::intValue))).orElse(0);
        System.out.println(integer);
    }
    //3
复制代码



分割数据块

collect的一个常用操作将Stream分解成两个集合。假如一个数字的Stream,我们可能希望将其分割成两个集合,一个是偶数集合,另外一个是奇数集合。我们首先想到的就是过滤操作,通过两次过滤操作,很简单的就完成了我们的需求。

但是这样操作起来有问题。首先,为了执行两次过滤操作,需要有两个流。其次,如果过滤操作复杂,每个流上都要执行这样的操作, 代码也会变得冗余。


这里我们就不得不说Collectors库中的partitioningBy方法,它接受一个流,并将其分成两部分:使用Predicate对象,指定条件并判断一个元素应该属于哪个部分,并根据布尔值返回一个Map到列表。因此对于key为true所对应的List中的元素,满足Predicate对象中指定的条件;同样,key为false所对应的List中的元素,不满足Predicate对象中指定的条件

public static void main(String[] args) {
        Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4)
                .collect(Collectors.partitioningBy(it -> it % 2 == 0));
        System.out.println("collectParti : " + collectParti);
    }
    //collectParti : {false=[1, 3], true=[2, 4]}


数据分组

数据分组是一种更自然的分割数据操作, 与将数据分成true和false两部分不同,可以使用任意值对数据分组。

调用Stream的collect方法,传入一个收集器,groupingBy接受一个分类函数,用来对数据分组,就像partitioningBy一样,接受一个 Predicate对象将数据分成true和false两部分。我们使用的分类器是一个Function对象,和map操作用到的一样。

Map<Boolean, List<Integer>> collectGroup= Stream.of(1, 2, 3, 4)
            .collect(Collectors.groupingBy(it -> it > 3));
System.out.println("collectGroup : " + collectGroup);// 打印结果
// collectGroup : {false=[1, 2, 3], true=[4]}
复制代码


字符串

Collectors.joining 收集Stream中的值,该方法可以方便地将Stream得到一个字符串。joining函数接受三个参数,分别表示允(用以分隔元素)、前缀和后缀。

String strJoin = Stream.of("1", "2", "3", "4")
        .collect(Collectors.joining(",", "[", "]"));
System.out.println("strJoin: " + strJoin);
// strJoin: [1,2,3,4]
复制代码


综合演练

前面,我们已经了解到Collector的强大,而且非常的使用。如果将他们组合起来,是不是更厉害呢?看前面举过的例子,在数据分组时,我们是得到的分组后的数据列表 collectGroup : {false=[1, 2, 3], true=[4]}。如果我们的要求更高点,我们不需要分组后的列表,只要得到分组后列表的个数就好了。

// 分割数据块
Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4)
        .collect(Collectors.partitioningBy(it -> it % 2 == 0));
Map<Boolean, Integer> mapSize = new HashMap<>();
collectParti.entrySet()
        .forEach(entry -> mapSize.put(entry.getKey(), entry.getValue().size()));
System.out.println("mapSize : " + mapSize);
// mapSize : {false=2, true=2}
复制代码


在partitioningBy方法中,有这么一个变形:

Map<Boolean, Long> partiCount = Stream.of(1, 2, 3, 4)
        .collect(Collectors.partitioningBy(it -> it.intValue() % 2 == 0,
                Collectors.counting()));
System.out.println("partiCount: " + partiCount);
// partiCount: {false=2, true=2}
复制代码


多级分组: 嵌套使用groupingBy即可

List<String> views = Lists.newArrayList("wsbsq","hello word","b8fw", "word", "wall", "ad");
Map<Object, Map<Object, List<String>>> res = views.stream().collect(groupingBy(str ->str.charAt(0),groupingBy(String::length)));
System.out.println(res);
// {a={2=[ad]}, b={4=[b8fw]}, w={4=[word, wall], 5=[wsbsq]}, h={10=[hello word]}}
复制代码


分組后取最小值

List<Integer> list= Arrays.asList(1,1,2,2,3,5,7,10,11,11,12);
        Map<Integer, Optional<Integer>> collect = list.stream().collect(groupingBy(Integer::intValue, Collectors.minBy(Comparator.comparing(Integer::intValue))));
        collect.forEach((key,value)->{
            System.out.println("key"+key+"      "+"value"+value);
        });
      //key1      valueOptional[1]
      //key2      valueOptional[2]
      //key3      valueOptional[3]
      //key5      valueOptional[5]
      //key7      valueOptional[7]
      //key10      valueOptional[10]
      //key11      valueOptional[11]
      //key12      valueOptional[12]
复制代码


相关文章
|
5天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
20 3
|
5天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
15 2
|
7天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
19 3
|
11天前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
46 1
|
19天前
|
编解码 Oracle Java
java9到java17的新特性学习--github新项目
本文宣布了一个名为"JavaLearnNote"的新GitHub项目,该项目旨在帮助Java开发者深入理解和掌握从Java 9到Java 17的每个版本的关键新特性,并通过实战演示、社区支持和持续更新来促进学习。
57 3
|
19天前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
32 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
19天前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
17 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
8天前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
24 6
|
5天前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
18 2
|
5天前
|
Java
Java Set以其“不重复”的特性,为我们提供了一个高效、简洁的处理唯一性约束数据的方式。
【10月更文挑战第16天】在Java编程中,Set接口确保集合中没有重复元素,每个元素都是独一无二的。HashSet基于哈希表实现,提供高效的添加、删除和查找操作;TreeSet则基于红黑树实现,不仅去重还能自动排序。通过这两个实现类,我们可以轻松处理需要唯一性约束的数据,提升代码质量和效率。
17 2