Java8 stream流操作(下)

本文是我在学习Java8的时候参考下面大佬的文章拷贝的,仅用于个人整理和学习用途
原文地址:[https://www.jianshu.com/p/e429c517e9cb]

本篇文章我们继续讲解流的其他操作
没有看过上篇文章的可以先点击进去学习一下 Java8 stream流操作(上)

本篇文章主要内容:

一种特化形式的流——数值流

  • Optional 类
  • 如何构建一个流
  • collect 方法
  • 并行流相关问题

一. 数值流

前面介绍的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 计算元素总和的方法其中暗含了装箱成本,map(Person::getAge) 方法过后流变成了 Stream 类型,而每个 Integer 都要拆箱成一个原始类型再进行 sum 方法求和,这样大大影响了效率。

针对这个问题 Java 8 有良心地引入了数值流 IntStream, DoubleStream, LongStream,这种流中的元素都是原始数据类型,分别是 int,double,long

1. 流与数值流的转换

流转换为数值流

  • mapToInt(T -> int) : return IntStream
  • mapToDouble(T -> double) : return DoubleStream
  • mapToLong(T -> long) : return LongStream
        //查询表中serial_number=6的数据
        List list = aTagMapper.selectList(new QueryWrapper().eq("serial_number",6));
        LongStream longStream = list.stream().mapToLong(ATag::getId);

数值流转换为流 boxed() 装箱

Stream boxed = list.stream().mapToLong(ATag::getId).boxed();

流转换为集合

List collect = list.stream().mapToLong(ATag::getId).boxed().collect(toList());
  • mapping()
    将集合中的某个字段拼接成一个新的数组
List newList = list.stream().collect(mapping(ATag::getNameZh, toList()));

也可以写成

List newList2 = list.stream().map(ATag::getNameZh).collect(toList());

2. 数值流方法

  • sum()
  • max()
  • min()
  • average() 等...
int sum = list.stream().mapToInt(x -> x.intValue()).sum();
int min = list.stream().mapToInt(ATag::getSerialNumber).min().getAsInt();  

3. 数值范围

IntStream 与 LongStream 拥有 range 和 rangeClosed 方法用于数值范围处理

  • IntStream : rangeClosed(int, int) / range(int, int)
  • LongStream : rangeClosed(long, long) / range(long, long)
    这两个方法的区别在于一个是闭区间,一个是半开半闭区间:
  • rangeClosed(1, 100) :[1, 100]
  • range(1, 100) :[1, 100)
    我们可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的数值流
        IntStream range = IntStream.rangeClosed(1, 100);
        int sum = range.sum();

二. Optional 类

NullPointerException 可以说是每一个 Java 程序员都非常讨厌看到的一个词,针对这个问题, Java 8 引入了一个新的容器类 Optional,可以代表一个值存在或不存在,这样就不用返回容易出问题的 null。之前文章的代码中就经常出现这个类,也是针对这个问题进行的改进。

Optional 类比较常用的几个方法有:

isPresent() :值存在时返回 true,反之 flase
get() :返回当前值,若值不存在会抛出异常
orElse(T) :值存在时返回该值,否则返回 T 的值
Optional 类还有三个特化版本 OptionalInt,OptionalLong,OptionalDouble,刚刚讲到的数值流中的 max 方法返回的类型便是这个Optional 类其中其实还有很多学问,讲解它说不定也要开一篇文章,这里先讲那么多,先知道基本怎么用就可以。

三. 构建流

之前我们得到一个流是通过一个原始数据源转换而来,其实我们还可以直接构建得到流。

1. 值创建流

Stream.of(T...) : Stream.of("aa", "bb") 生成流

        Stream integerStream = Stream.of(1, 2, 3, 4);
  • Stream.empty() : 生成空流

2. 数组与流转换

参考文章:int []数组与List互相转换
根据参数的数组类型创建对应的流:

  • Arrays.stream(T[ ])
  • Arrays.stream(int[ ])
  • Arrays.stream(double[ ])
  • Arrays.stream(long[ ])
数组转换为流:
        int[] ints = {1, 2, 3, 4};
        IntStream stream = Arrays.stream(ints);

然后运用上面学到的装箱与转换集合,灵活地进行数组 --> 数值流 -->数组 --> 集合之间转换
这里如果int[] 为 Integer[] 则数组转换为集合不需要boxed()装箱操作

        int[] ints = {1, 2, 3, 4};
        IntStream integerStream = Arrays.stream(ints);
        Integer[] integers = integerStream.boxed().toArray(Integer[]::new);
        List list = Arrays.stream(ints).boxed().collect(toList());

输出:

值得注意的是,还可以规定索引只取数组的某部分,用到的是Arrays.stream(T[], int, int)

输出:

3. 文件生成流

      Stream stream = Files.lines(Paths.get("data.txt"));

每个元素是给定文件的其中一行
这个方法暂时不知道怎么用,文件的路径以及位置是怎样的有大佬懂的请留言指教一下

4. 函数生成流

两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流,配合limit()使用

  • iterate : 依次对每个新生成的值应用函数
  • generate :接受一个函数,生成一个新的值
      Stream.iterate(0, n -> n + 2).limit(10).collect(toList())
      Stream.generate(Math::random).limit(10).collect(toList())

输出结果:

四. collect 收集数据

coollect 方法作为终端操作,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操作
最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中(个人理解就是将流转换为集合)

  • toList
  • toSet
  • toCollection
  • toMap
   List newlist = list.stream.collect(toList());
//如果 Map 的 Key 重复了,可是会报错的哦
Map map = list.stream().collect(toMap(Person::getAge, p -> p));

2. 汇总

(1)counting
用于计算总和:

int sum = list.stream().collect(summingInt(Person::getAge));

也可以简化为:

int sum = list.stream().mapToInt(Person::getAge).sum();

也可以写成

int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();

推荐使用第二种

(3)averagingInt,averagingLong,averagingDouble

用于求取平均值

Double average = list.stream().collect(averagingInt(Person::getAge));

当然也可以这样写

OptionalDouble average = list.stream().mapToInt(Person::getAge).average();

不过要注意的是,这两种返回的值是不同类型的

(4)summarizingInt,summarizingLong,summarizingDouble

这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型

      IntSummaryStatistics intSummaryStatistics = list.stream().collect(summarizingInt(Integer::intValue));
      IntSummaryStatistics intSummaryStatistics = list.stream().collect(summarizingInt(ATag::getSerialNumber));

然后使用这个IntSummaryStatistics获取所需要的值,比如统计个数,均值,求和,最值,等...

3. 取最值

maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数

      Optional optional = list.stream().collect(maxBy(comparing(Person::getAge)));

我们也可以直接使用 max 方法获得同样的结果

      Optional optional = list.stream().max(comparing(Person::getAge));

4.1 joining 连接字符串

也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder

List list = ImmutableList.of("Tom","Jack","Speike");
list.stream().collect(joining(","));
结果:Tom,Jack,Speike
list.stream().collect(joining(" and ", "Today ", " play games."));
结果:Today Tom and Jack and Speike play games.

tips:

  • 当我们使用StringJoiner(CharSequence delimiter)初始化一个StringJoiner的时候,这个delimiter其实是分隔符,并不是可变字符串的初始值。
  • StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)的第二个和第三个参数分别是拼接后的字符串的前缀和后缀。
    参考文章:Java——StringJoiner

5. groupingBy 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型

      Map> map = list.stream().collect(groupingBy(Person::getAge));

例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组
另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List 类型)

多级分组

groupingBy 可以接受一个第二参数实现多级分组:

Map>> map = list.stream().collect(groupingBy(ATag::getSerialNumber, groupingBy(ATag::getCreateTime)));

例如上面代码先按序号分组,再按创建时间分组,当然在时间分组后面再添加分组groupingBy(...)
其中返回的 Map 键为 Integer 类型,值为 Map> 类型,即参数中 groupBy(...) 返回的类型
tips:
返回的Map为无序的集合,根据业务需求可以将Map转换为有序的LinkedHashMap

LinkedHashMap> map = list.stream().collect(groupingBy(ATag::getSerialNumber, LinkedHashMap::new, toList()));
按组收集数据
Map map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));

该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map
根据这个方法,我们可以知道,前面我们写的:

groupingBy(Person::getAge)

其实等同于:

groupingBy(Person::getAge, toList())

6. partitioningBy 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

Map> map = list.stream().collect(partitioningBy(p -> p.getAge() <= 20));

结果:
打印输出

{
    false=[Person{name='mike', age=25}, Person{name='tom', age=30}], 
    true=[Person{name='jack', age=20}]
}

同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。

你可能感兴趣的:(Java8 stream流操作(下))