Java8 Stream

上一篇文章我讲解 Stream 流的基本原理,以及它与集合的区别关系,讲了那么多抽象的,本篇文章我们开始实战,讲解流的各个方法以及各种操作
没有看过上篇文章的可以先点击进去学习一下 简洁又快速地处理集合——Java8 Stream(上),当然你直接看这篇也可以,不过了解其本身才能更融会贯通哦。
值得注意的是:学习 Stream 之前必须先学习 lambda 的相关知识。本文也假设读者已经掌握 lambda 的相关知识。

本篇文章主要内容:
流基本的常用方法
一种特化形式的流——数值流
Optional 类
如何构建一个流
collect 方法
并行流相关问题
一. 一般方法
首先我们先创建一个 Person 泛型的 List

List list = new ArrayList<>();
list.add(new Person(“jack”, 20));
list.add(new Person(“mike”, 25));
list.add(new Person(“tom”, 30));
Person 类包含年龄和姓名两个成员变量

private String name;
private int age;

  1. stream() / parallelStream()
    最常用到的方法,将集合转换为流

List list = new ArrayList();
// return Stream
list.stream();
而 parallelStream() 是并行流方法,能够让数据集执行并行操作,后面会更详细地讲解

  1. filter(T -> boolean)
    保留 boolean 为 true 的元素

保留年龄为 20 的 person 元素
list = list.stream()
.filter(person -> person.getAge() == 20)
.collect(toList());

打印输出 [Person{name=‘jack’, age=20}]
collect(toList()) 可以把流转换为 List 类型,这个以后会讲解

  1. distinct()
    去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的

如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name=‘jack’, age=20}, Person{name=‘jack’, age=20}] 这样的情况是不会处理的

  1. sorted() / sorted((T, T) -> int)
    如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream

反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口

根据年龄大小来比较:
list = list.stream()
.sorted((p1, p2) -> p1.getAge() - p2.getAge())
.collect(toList());
当然这个可以简化为

list = list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.collect(toList());
5. limit(long n)
返回前 n 个元素

list = list.stream()
.limit(2)
.collect(toList());

打印输出 [Person{name=‘jack’, age=20}, Person{name=‘mike’, age=25}]
6. skip(long n)
去除前 n 个元素

list = list.stream()
.skip(2)
.collect(toList());

打印输出 [Person{name=‘tom’, age=30}]
tips:

用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素
limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素
list = list.stream()
.limit(2)
.skip(1)
.collect(toList());

打印输出 [Person{name=‘mike’, age=25}]
7. map(T -> R)
将流中的每一个元素 T 映射为 R(类似类型转换)

List newlist = list.stream().map(Person::getName).collect(toList());
newlist 里面的元素为 list 中每一个 Person 对象的 name 变量

  1. flatMap(T -> Stream
    将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流

List list = new ArrayList<>();
list.add(“aaa bbb ccc”);
list.add(“ddd eee fff”);
list.add(“ggg hhh iii”);

list = list.stream().map(s -> s.split(" “)).flatMap(Arrays::stream).collect(toList());
上面例子中,我们的目的是把 List 中每个字符串元素以” "分割开,变成一个新的 List

  1. anyMatch(T -> boolean)
    流中是否有一个元素匹配给定的 T -> boolean 条件

是否存在一个 person 对象的 age 等于 20:
boolean b = list.stream().anyMatch(person -> person.getAge() == 20);
10. allMatch(T -> boolean)
流中是否所有元素都匹配给定的 T -> boolean 条件

  1. noneMatch(T -> boolean)
    流中是否没有元素匹配给定的 T -> boolean 条件

  2. findAny() 和 findFirst()
    findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
    findFirst():找到第一个元素
    值得注意的是,这两个方法返回的是一个 Optional,它是一个容器类,能代表一个值存在或不存在,这个后面会讲到

  3. reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)
    用于组合流中的元素,如求和,求积,求最大值等

计算年龄总和:
int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
与之相同:
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
其中,reduce 第一个参数 0 代表起始值为 0,lambda (a, b) -> a + b 即将两值相加产生一个新值

同样地:

计算年龄总乘积:
int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);
当然也可以

Optional sum = list.stream().map(Person::getAge).reduce(Integer::sum);
即不接受任何起始值,但因为没有初始值,需要考虑结果可能不存在的情况,因此返回的是 Optional 类型

  1. count()
    返回流中元素个数,结果为 long 类型

  2. collect()
    收集方法,我们很常用的是 collect(toList()),当然还有 collect(toSet()) 等,参数是一个收集器接口,这个后面会另外讲

  3. forEach()
    返回结果为 void,很明显我们可以通过它来干什么了,比方说:

16. unordered()

还有这个比较不起眼的方法,返回一个等效的无序流,当然如果流本身就是无序的话,那可能就会直接返回其本身

打印各个元素:
list.stream().forEach(System.out::println);
再比如说 MyBatis 里面访问数据库的 mapper 方法:

向数据库插入新元素:
list.stream().forEach(PersonMapper::insertPerson);
二. 数值流
前面介绍的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 计算元素总和的方法其中暗含了装箱成本,map(Person::getAge) 方法过后流变成了 Stream

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

  1. 流与数值流的转换
    流转换为数值流
    mapToInt(T -> int) : return IntStream
    mapToDouble(T -> double) : return DoubleStream
    mapToLong(T -> long) : return LongStream
    IntStream intStream = list.stream().mapToInt(Person::getAge);
    当然如果是下面这样便会出错

LongStream longStream = list.stream().mapToInt(Person::getAge);
因为 getAge 方法返回的是 int 类型(返回的如果是 Integer,一样可以转换为 IntStream)

数值流转换为流
很简单,就一个 boxed

Stream stream = intStream.boxed();
2. 数值流方法
下面这些方法作用不用多说,看名字就知道:

sum()
max()
min()
average() 等…
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 的数值流

求 1 到 10 的数值总和:
IntStream intStream = IntStream.rangeClosed(1, 10);
int sum = intStream.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 stream = Stream.of(“aaa”, “bbb”, “ccc”);
    Stream.empty() : 生成空流
  2. 数组创建流
    根据参数的数组类型创建对应的流:

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

只取索引第 1 到第 2 位的:
int[] a = {1, 2, 3, 4};
Arrays.stream(a, 1, 3).forEach(System.out :: println);

打印 2 ,3
3. 文件生成流
Stream stream = Files.lines(Paths.get(“data.txt”));
每个元素是给定文件的其中一行

  1. 函数生成流
    两个方法:

iterate : 依次对每个新生成的值应用函数
generate :接受一个函数,生成一个新的值
Stream.iterate(0, n -> n + 2)
生成流,首元素为 0,之后依次加 2

Stream.generate(Math :: random)
生成流,为 0 到 1 的随机双精度数

Stream.generate(() -> 1)
生成流,元素全为 1
五. collect 收集数据
coollect 方法作为终端操作,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操作

  1. 收集
    最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中

toList
toSet
toCollection
List newlist = list.stream.collect(toList());
2. 汇总
(1)counting
用于计算总和:

long l = list.stream().collect(counting());
没错,你应该想到了,下面这样也可以:

long l = list.stream().count();
推荐第二种

(2)summingInt ,summingLong ,summingDouble
summing,没错,也是计算总和,不过这里需要一个函数参数

计算 Person 年龄总和:

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 l = list.stream().collect(summarizingInt(Person::getAge));
IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,可以通过下面这些方法获得相应的数据

  1. 取最值
    maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数

Optional optional = list.stream().collect(maxBy(comparing(Person::getAge)));
我们也可以直接使用 max 方法获得同样的结果

Optional optional = list.stream().max(comparing(Person::getAge));
4. joining 连接字符串
也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder

String s = list.stream().map(Person::getName).collect(joining());

结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));

结果:jack,mike,tom
joining 还有一个比较特别的重载方法:

String s = list.stream().map(Person::getName).collect(joining(" and ", "Today “, " play games.”));

结果:Today jack and mike and tom play games.
即 Today 放开头,play games. 放结尾,and 在中间连接各个字符串

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

Map<Integer, List> map = list.stream().collect(groupingBy(Person::getAge));
例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组

另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List

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

Map<Integer, Map<T, List>> map = list.stream().collect(groupingBy(Person::getAge, groupBy(…)));
其中返回的 Map 键为 Integer 类型,值为 Map<T, List

按组收集数据
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));
该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map<Integer, Integer>

根据这个方法,我们可以知道,前面我们写的:

groupingBy(Person::getAge)
其实等同于:

groupingBy(Person::getAge, toList())
6. partitioningBy 分区
分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

根据年龄是否小于等于20来分区
Map<Boolean, List> 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 的多重分区等等操作。

六. 并行
之前我就讲到了 parallelStream 方法能生成并行流,因此你通常可以使用 parallelStream 来代替 stream 方法,但是并行的性能问题非常值得我们思考

比方说下面这个例子

int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);
我们通过这样一行代码来计算 1 到 100 的所有数的和,我们使用了 parallel 来实现并行。

但实际上是,这样的计算,效率是非常低的,比不使用并行还低!一方面是因为装箱问题,这个前面也提到过,就不再赘述,还有一方面就是 iterate 方法很难把这些数分成多个独立块来并行执行,因此无形之中降低了效率。

流的可分解性
这就说到流的可分解性问题了,使用并行的时候,我们要注意流背后的数据结构是否易于分解。比如众所周知的 ArrayList 和 LinkedList,明显前者在分解方面占优。

我们来看看一些数据源的可分解性情况

数据源

可分解性

ArrayList

极佳

LinkedList

IntStream.range

极佳

Stream.iterate

HashSet

TreeSet

顺序性
除了可分解性,和刚刚提到的装箱问题,还有一点值得注意的是一些操作本身在并行流上的性能就比顺序流要差,比如:limit,findFirst,因为这两个方法会考虑元素的顺序性,而并行本身就是违背顺序性的,也是因为如此 findAny 一般比 findFirst 的效率要高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8引入了Stream API,它是一种处理集合数据的新方式。Stream API提供了一种流式操作的方式,可以对集合进行过滤、映射、排序、聚合等操作,使得代码更加简洁、易读和高效。 Stream是一个来自数据源的元素队列并支持聚合操作。它可以是集合、数组、I/O channel、产生器等。Stream操作可以顺序执行,也可以并行执行。 Java 8 Stream API的特点包括: 1. 延迟执行:Stream操作通常是延迟执行的,只有在终止操作时才会触发实际的计算。 2. 内部迭代:Stream API使用内部迭代的方式,不需要显式地编写循环,使得代码更加简洁。 3. 函数式编程:Stream API支持函数式编程风格,可以通过Lambda表达式来定义操作。 4. 并行处理:Stream API提供了并行处理的能力,可以充分利用多核处理器的优势,提高处理速度。 使用Stream API可以通过一系列的中间操作和终止操作来对集合进行处理。中间操作包括过滤、映射、排序等操作,终止操作包括聚合、收集、遍历等操作。 下面是一些常用的Stream操作方法: 1. filter(Predicate<T> predicate):根据指定条件过滤元素。 2. map(Function<T, R> mapper):将元素进行映射换。 3. sorted(Comparator<T> comparator):对元素进行排序。 4. distinct():去除重复的元素。 5. limit(long maxSize):限制元素的数量。 6. skip(long n):跳过指定数量的元素。 7. forEach(Consumer<T> action):对每个元素执行指定操作。 8. collect(Collector<T, A, R> collector):将元素收集到集合中。 9. reduce(BinaryOperator<T> accumulator):对元素进行归约操作。 10. parallel():启用并行处理。 以上只是Stream API的一部分常用操作,还有更多的操作方法可以根据具体需求使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值