上一篇文章我讲解 Stream 流的基本原理,以及它与集合的区别关系,讲了那么多抽象的,本篇文章咱们开始实战,讲解流的各个方法以及各类操做程序员
没有看过上篇文章的能够先点击进去学习一下 简洁又快速地处理集合——Java8 Stream(上),固然你直接看这篇也能够,不过了解其自己才能更融会贯通哦。数据库
值得注意的是:学习 Stream 以前必须先学习 lambda 的相关知识。本文也假设读者已经掌握 lambda 的相关知识。编程
首先咱们先建立一个 Person 泛型的 List后端
List<Person> 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;
最经常使用到的方法,将集合转换为流数据结构
List list = new ArrayList(); // return Stream<E> list.stream();
而 parallelStream() 是并行流方法,可以让数据集执行并行操做,后面会更详细地讲解app
保留 boolean 为 true 的元素dom
保留年龄为 20 的 person 元素 list = list.stream() .filter(person -> person.getAge() == 20) .collect(toList()); 打印输出 [Person{name='jack', age=20}]
collect(toList()) 能够把流转换为 List 类型,这个之后会讲解函数式编程
去除重复元素,这个方法是经过类的 equals 方法来判断两个元素是否相等的函数
如例子中的 Person 类,须要先定义好 equals 方法,否则相似[Person{name='jack', age=20}, Person{name='jack', age=20}]
这样的状况是不会处理的
若是流中的元素的类实现了 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());
返回前 n 个元素
list = list.stream() .limit(2) .collect(toList()); 打印输出 [Person{name='jack', age=20}, Person{name='mike', age=25}]
去除前 n 个元素
list = list.stream() .skip(2) .collect(toList()); 打印输出 [Person{name='tom', age=30}]
tips:
list = list.stream() .limit(2) .skip(1) .collect(toList()); 打印输出 [Person{name='mike', age=25}]
将流中的每个元素 T 映射为 R(相似类型转换)
List<String> newlist = list.stream().map(Person::getName).collect(toList());
newlist 里面的元素为 list 中每个 Person 对象的 name 变量
将流中的每个元素 T 映射为一个流,再把每个流链接成为一个流
List<String> 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
首先 map 方法分割每一个字符串元素,但此时流的类型为 Stream<String[ ]>,由于 split 方法返回的是 String[ ] 类型;因此咱们须要使用 flatMap 方法,先使用
Arrays::stream
将每一个 String[ ] 元素变成一个 Stream
流中是否有一个元素匹配给定的 T -> boolean
条件
是否存在一个 person 对象的 age 等于 20: boolean b = list.stream().anyMatch(person -> person.getAge() == 20);
流中是否全部元素都匹配给定的 T -> boolean
条件
流中是否没有元素匹配给定的 T -> boolean
条件
值得注意的是,这两个方法返回的是一个 Optional
用于组合流中的元素,如求和,求积,求最大值等
计算年龄总和: 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<Integer> sum = list.stream().map(Person::getAge).reduce(Integer::sum);
即不接受任何起始值,但由于没有初始值,须要考虑结果可能不存在的状况,所以返回的是 Optional 类型
返回流中元素个数,结果为 long 类型
收集方法,咱们很经常使用的是 collect(toList())
,固然还有 collect(toSet())
等,参数是一个收集器接口,这个后面会另外讲
返回结果为 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
IntStream intStream = list.stream().mapToInt(Person::getAge);
固然若是是下面这样便会出错
LongStream longStream = list.stream().mapToInt(Person::getAge);
由于 getAge 方法返回的是 int 类型(返回的若是是 Integer,同样能够转换为 IntStream)
很简单,就一个 boxed
Stream<Integer> stream = intStream.boxed();
下面这些方法做用不用多说,看名字就知道:
IntStream 与 LongStream 拥有 range 和 rangeClosed 方法用于数值范围处理
这两个方法的区别在于一个是闭区间,一个是半开半闭区间:
咱们能够利用 IntStream.rangeClosed(1, 100)
生成 1 到 100 的数值流
求 1 到 10 的数值总和: IntStream intStream = IntStream.rangeClosed(1, 10); int sum = intStream.sum();
NullPointerException 能够说是每个 Java 程序员都很是讨厌看到的一个词,针对这个问题, Java 8 引入了一个新的容器类 Optional,能够表明一个值存在或不存在,这样就不用返回容易出问题的 null。以前文章的代码中就常常出现这个类,也是针对这个问题进行的改进。
Optional 类比较经常使用的几个方法有:
Optional 类还有三个特化版本 OptionalInt,OptionalLong,OptionalDouble,刚刚讲到的数值流中的 max 方法返回的类型即是这个
Optional 类其中其实还有不少学问,讲解它说不定也要开一篇文章,这里先讲那么多,先知道基本怎么用就能够。
以前咱们获得一个流是经过一个原始数据源转换而来,其实咱们还能够直接构建获得流。
生成一个字符串流 Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
根据参数的数组类型建立对应的流:
值得注意的是,还能够规定只取数组的某部分,用到的是Arrays.stream(T[], int, int)
只取索引第 1 到第 2 位的: int[] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println); 打印 2 ,3
Stream<String> stream = Files.lines(Paths.get("data.txt"));
每一个元素是给定文件的其中一行
两个方法:
Stream.iterate(0, n -> n + 2) 生成流,首元素为 0,以后依次加 2 Stream.generate(Math :: random) 生成流,为 0 到 1 的随机双精度数 Stream.generate(() -> 1) 生成流,元素全为 1
coollect 方法做为终端操做,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操做
最经常使用的方法,把流中全部元素收集到一个 List, Set 或 Collection 中
List newlist = list.stream.collect(toList());
用于计算总和:
long l = list.stream().collect(counting());
没错,你应该想到了,下面这样也能够:
long l = list.stream().count();
推荐第二种
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();
推荐第二种
因而可知,函数式编程一般提供了多种方式来完成同一种操做
看名字就知道,求平均数
Double average = list.stream().collect(averagingInt(Person::getAge));
固然也能够这样写
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
不过要注意的是,这两种返回的值是不一样类型的
这三个方法比较特殊,好比 summarizingInt 会返回 IntSummaryStatistics 类型
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,能够经过下面这些方法得到相应的数据
maxBy,minBy 两个方法,须要一个 Comparator 接口做为参数
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
咱们也能够直接使用 max 方法得到一样的结果
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
也是一个比较经常使用的方法,对流里面的字符串元素进行链接,其底层实现用的是专门用于字符串链接的 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 在中间链接各个字符串
groupingBy 用于将数据分组,最终返回一个 Map 类型
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));
例子中咱们按照年龄 age 分组,每个 Person 对象中年龄相同的归为一组
另外能够看出,Person::getAge
决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List
groupingBy 能够接受一个第二参数实现多级分组:
Map<Integer, Map<T, List<Person>>> 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())
分区与分组的区别在于,分区是按照 true 和 false 来分的,所以partitioningBy 接受的参数的 lambda 也是 T -> boolean
根据年龄是否小于等于20来分区 Map<Boolean, List<Person>> 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 的效率要高。