先贴上几个案例,水平高超的同学可以挑战一下:
- 从员工集合中筛选出salary大于8000的员工,并放置到新的集合里。
- 统计员工的最高薪资、平均薪资、薪资之和。
- 将员工按薪资从高到低排序,同样薪资者年龄小者在前。
- 将员工按性别分类,将员工按性别和地区分类,将员工按薪资是否高于8000分为两部分。
用传统的迭代处理也不是很难,但代码就显得冗余了,跟Stream相比高下立判。
Stream概述
Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。
那么什么是Stream?
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
Stream可以由数组或集合创建,对流的操作分为两种:
- 中间操作,每次返回一个新的流,可以有多个。
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
另外,Stream有几个特性:
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
Stream的优势
代码简洁:函数式编程写出的代码简洁且意图明确,使用Stream接口让你从此告别for循环。
多核友好:Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。
常见的stream接口继承关系如图
图中4种stream接口继承自BaseStream,其中IntStream, LongStream, DoubleStream对应三种基本类型(int, long, double,注意不是包装类型),Stream对应所有剩余类型的Stream视图。为不同数据类型设置不同Stream接口,可以
- 提高性能,
- 增加特定接口函数。
你可能会奇怪为什么不把IntStream等设计成Stream的子接口?毕竟这接口中的方法名大部分是一样的。答案是这些方法的名字虽然相同,但是返回类型不同,如果设计成父子接口关系,这些方法将不能共存,因为Java不允许只有返回类型不同的方法重载。
Stream的原理和特性
无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
分类
1)、无状态(元素处理不受之前元素影响)
filter()、map()、mapToInt()、mapToLong()、mapToDouble()、flatMap()、flatMapToInt()、flatMapToLong()、flatMapToDouble()、peek()、unordered()
2)、有状态(必须拿到全部元素才能继续下去)
distinct()、sorted()、limit()、skip()
Stream操作的执行过程
Stream的创建
Stream可以通过集合数组创建。
1、通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
2、使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
3、使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
输出结果:
0 3 6 9
0.6796156909271994
0.1914314208854283
0.8116932592396652
of方法:有两个重载方法,一个接受变长参数,一个接口单一值。
Stream integerStream = Stream.of(1, 2, 3, 5);
Stream stringStream = Stream.of("taobao");
// generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier
Stream.generate(new Supplier() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。
iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。
Collection接口的默认方法
Collection接口有一个stream方法,所以其所有子类都都可以获取对应的Stream对象。
- 调用Collection.stream()或者Collection.parallelStream()方法
- 调用Arrays.stream(T[] array)方法
stream和parallelStream的简单区分
stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:
如果流中的数据量足够大,并行流可以加快处速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:
Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();
Stream的使用
对stream的操作分为为两类,中间操作(intermediate operations)和结束操作(terminal operations),二者特点是:
- 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。中间操作又可以分为无状态的(Stateless)和有状态的(Stateful),无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;
- 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。结束操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。下图汇总了Stream接口的部分常见方法:
区分中间操作和结束操作最简单的方法,就是看方法的返回值,返回值为stream的大都是中间操作,否则是结束操作。
Stream 使用流的方式进行操作集合数据,具体的使用方法有数据筛选&切片、映射、排序。
Stream的三个操作步骤:
1、创建Stream
2、中间操作
筛选&切片:
filter -接受Lambda,从流中排除某些元素。
limit -截断流,使其元素不超过给定的数量。
skip(n) -跳过元素,返回一个扔掉前n个元素的流。若流中元素不及n个,则返回一个空流,与limit互补。
distinct -筛选,通过流所产生元素的hashcode()和equals()去掉重复。
映射:
map -接受Lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其他映射成一个新的元素。
flatmap -接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连成一个流。
排序:
sorted() -检查是否匹配所有元素。
sorted(comparator com) -定制排序。
allmatch -检查是否匹配所有元素。
anymatch -检查是否至少匹配一个元素。
nonematch -检查是否没有匹配所有元素。
findfirst -返回第一个元素。
findany -返回当前流中任意元素。
count -返回流中总个数
max -返回流中最大值
min -返回流中最小值
3、终止操作(终端操作)
例:
List
list.stream().sorted().forEach(System.out::println);
总结:Stream 的作用简单来说就是对一个数据集合进行一系列中间操作之后得到我们最终想要的一个数据流。
案例使用的员工类
这是后面案例中使用的员工类:
class Person {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; //性别
private String area; // 地区
// 构造方法
public Person(String name, int salary, int age,String sex,String area) {
this.name = name;
this.salary = salary;
this.age = age;
this.sex = sex;
this.area = area;
}
// 省略了get和set,请自行添加
}
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));
3.1 遍历/匹配(foreach/find/match)
Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。
// import已省略,请自行添加,后面代码亦是
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
// 遍历输出符合条件的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);
// 匹配第一个
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
// 匹配任意(适用于并行流)
Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
// 是否包含符合特定条件的元素
boolean anyMatch = list.stream().anyMatch(x -> x < 6);
System.out.println("匹配第一个值:" + findFirst.get());
System.out.println("匹配任意一个值:" + findAny.get());
System.out.println("是否存在大于6的值:" + anyMatch);
}
}
输出结果
7 9 8 匹配第一个值:7 匹配任意一个值:8 是否存在大于6的值:true
集合的遍历 forEach 方法:
public void testForEach(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.forEach(s-> System.out.println(s));
}
输出结果
1 2 3
函数原型为void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。
Stream
stream.forEach(System.out::println);
3.2 筛选(filter)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
案例一:筛选出Integer集合中大于7的元素,并打印出来
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
Stream<Integer> stream = list.stream();
stream.filter(x -> x > 7).forEach(System.out::println);
}
}
预期结果:
8 9
案例二:筛选员工中工资高于8000的人,并形成新的集合。 形成新集合依赖collect(收集),后文有详细介绍。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName)
.collect(Collectors.toList());
System.out.print("高于8000的员工姓名:" + fiterList);
}
}
运行结果:
高于8000的员工姓名:[Tom, Anni, Owen]
Filter 为过滤的意思,只要满足 Filter 表达式的数据就可以留下来,不满足的数据被过滤掉。
public void testFilter() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream()
// 过滤掉我们希望留下来的值
// 表示我们希望字符串是 1 能留下来
// 其他的过滤掉
.filter(str -> "1".equals(str))
.collect(Collectors.toList());
}
函数原型为Stream
List<String> beginningWithNumbers
= Stream.of("a", "1abc", "abc1")
.filter(value -> isDigit(value.charAt(0)))
.collect(toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
若要重构遗留代码,for循环中的if条件语句就是一个很强的信号,可用filter方法替代。
filter()
对流中的元素按照给定的函数过滤,生成新的符合过滤条件的流。
Integer[] arr = {1,2,3,4,5,6,7,8,9,10};
Stream.of(arr).filter(e->e>=5).forEach(e-> System.out.println(e));
//输出:
5
6
7
8
9
10
3.3 聚合(max/min/count)
max、min、count这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。
Stream聚合max/min/count
「案例一:获取String集合中最长的元素。」
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
Optional<String> max = list.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + max.get());
}
}
输出结果
最长的字符串:weoujgsd
「案例二:获取Integer集合中的最大值。」
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);
// 自然排序
Optional<Integer> max = list.stream().max(Integer::compareTo);
// 自定义排序
Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println("自然排序的最大值:" + max.get());
System.out.println("自定义排序的最大值:" + max2.get());
}
}
输出结果
自然排序的最大值:11
自定义排序的最大值:11
「案例三:获取员工工资最高的人。」
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最大值:" + max.get().getSalary());
}
}
输出结果
员工工资最大值:9500
「案例四:计算Integer集合中大于6的元素的个数。」
import java.util.Arrays;
import java.util.List;
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
long count = list.stream().filter(x -> x > 6).count();
System.out.println("list中大于6的元素个数:" + count);
}
}
输出结果
list中大于6的元素个数:4
3.4 映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
案例一:英文字符串数组的元素全部改为大写。整数数组每个元素+3。
public class StreamTest {
public static void main(String[] args) {
String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());
System.out.println("每个元素大写:" + strList);
System.out.println("每个元素+3:" + intListNew);
}
}
输出结果:
每个元素大写:[ABCD, BCDD, DEFDE, FTR]
每个元素+3:[4, 6, 8, 10, 12, 14]
案例二:将员工的薪资全部增加1000。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
// 不改变原来员工集合的方式
List<Person> personListNew = personList.stream().map(person -> {
Person personNew = new Person(person.getName(), 0, 0, null, null);
personNew.setSalary(person.getSalary() + 10000);
return personNew;
}).collect(Collectors.toList());
System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());
// 改变原来员工集合的方式
List<Person> personListNew2 = personList.stream().map(person -> {
person.setSalary(person.getSalary() + 10000);
return person;
}).collect(Collectors.toList());
System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
}
}
输出结果:
一次改动前:Tom–>8900
一次改动后:Tom–>18900
二次改动前:Tom–>18900
二次改动后:Tom–>18900
案例三:将两个字符数组合并成一个新的字符数组。
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前的集合:" + list);
System.out.println("处理后的集合:" + listNew);
}
}
输出结果:
处理前的集合:[m-k-l-a, 1-3-5]
处理后的集合:[m, k, l, a, 1, 3, 5]
Map
函数原型为
List<String> collected = Stream.of("a", "b", "hello")
.map(String::toUpperCase)
.collect(toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
若要重构遗留代码,for循环有一个函数需要将一种类型的值转换成另外一种类型,map操作就可以使用该函数。
map()
对流中的每个元素按照给定的函数进行转换操作,生成新的流只包含转换后的元素。
String[] arr = {“apple”,”banana”,”orange”};
Stream.of(arr).map(e ->e.length()).forEach(e->System.out.println(e));
//输出:
5
6
6
mapToInt()、mapToLong()、mapToDouble()是map()的是三个变种方法,主要作用是免除自动拆箱装箱的额外消耗。
flatMap()
函数原型为
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.flatMap(Collection::stream)
.collect(toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);
flatMap()
对流中的每个元素进行扁平化提取,组成新的流。
map()与flatMap()对比:map提取流中的引用组成新的流,而flatMap提取流中的元素组成新的流。
String[] arr = {"1-2-3-4","5-6-7"};
Stream.of(arr).map(e->Stream.of(e.split("-"))).forEach(e-> System.out.println(e));
Stream.of(arr).flatMap(e->Stream.of(e.split("-"))).forEach(e-> System.out.println(e));
List<List<String>> lists = new ArrayList<>();
List<String> list1 = Arrays.asList("a","b","c");
List<String> list2 = Arrays.asList("d","e","f");
lists.add(list1);
lists.add(list2);
lists.stream().flatMap(e->e.stream()).forEach(e-> System.out.println(e));
//输出:
java.util.stream.ReferencePipeline$Head@5680a178
java.util.stream.ReferencePipeline$Head@5fdef03a
1
2
3
4
5
6
7
a
b
c
d
e
f
flatMapToInt()、flatMapToLong()、flatMapToDouble()是flatMap()的是三个变种方法,主要作用是免除自动拆箱装箱的额外消耗。
String[] arr = {"1","2","3","4","5","6"};
Stream.of(arr).flatMapToDouble(e-> DoubleStream.of(Double.parseDouble(e))).forEach(e-> System.out.println("value:"+e+",type:"+ToolUtil.getType(e)));
//输出:
value:1.0,type:class java.lang.Double
value:2.0,type:class java.lang.Double
value:3.0,type:class java.lang.Double
value:4.0,type:class java.lang.Double
value:5.0,type:class java.lang.Double
value:6.0,type:class java.lang.Double
3.5 归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
案例一:求Integer集合的元素之和、乘积和最大值。
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
// 求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 求和方式3
Integer sum3 = list.stream().reduce(0, Integer::sum);
// 求乘积
Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
// 求最大值方式1
Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
// 求最大值写法2
Integer max2 = list.stream().reduce(1, Integer::max);
System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("list求积:" + product.get());
System.out.println("list求和:" + max.get() + "," + max2);
}
}
输出结果:
list求和:29,29,29
list求积:2112
list求和:11,11
案例二:求所有员工的工资之和和最高工资。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
personList.add(new Person("Owen", 9500, 25, "male", "New York"));
personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
// 求工资之和方式1:
Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
// 求工资之和方式2:
Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
(sum1, sum2) -> sum1 + sum2);
// 求工资之和方式3:
Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);
// 求最高工资方式1:
Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
Integer::max);
// 求最高工资方式2:
Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
(max1, max2) -> max1 > max2 ? max1 : max2);
System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
System.out.println("最高工资:" + maxSalary + "," + maxSalary2);
}
}
输出结果:
工资之和:49300,49300,49300
最高工资:9500,9500
reduce 方法允许我们在循环里面叠加计算值:
public void testReduce(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream()
.map(s -> Integer.valueOf(s))
// s1 和 s2 表示循环中的前后两个数
.reduce((s1,s2) -> s1+s2)
.orElse(0);
list.stream()
.map(s -> Integer.valueOf(s))
// 第一个参数表示基数,会从 100 开始加
.reduce(100,(s1,s2) -> s1+s2);
}
reduce()
规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作。Stream类库有两个通用的规约操作reduce()和collect(),也有一些为简化书写而设计的专用规约操作,比如sum()、max()、min()、count()等。
reduce操作可以实现从一组元素中生成一个值,sum()、max()、min()、count()等都是reduce操作,将他们单独设为函数只是因为常用。reduce()的方法定义有三种重载形式:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
虽然函数定义越来越长,但语义不曾改变,多的参数只是为了指明初始值(参数identity),或者是指定并行执行时多个部分结果的合并方式(参数combiner)。reduce()最常用的场景就是从一堆值中生成一个值。示例1:从一组单词中找出最长的单词。这里“大”的含义就是“长”。
// 找出最长的单词
Stream<String> stream = Stream.of("reduce", "optional", "test", "stream");
Optional<String> longest = stream.reduce((s1, s2) -> s1.length() >= s2.length() ? s1 : s2);
//Optional<String> longest = stream.max(Comparator.comparingInt(String::length));
assertEquals("optional", longest.get());
示例2:数字求和
int sum = Stream.of(1, 2, 3, 4)
.reduce(0, (acc, element) -> acc + element);
assertEquals(10, sum);
示例3:求字符串数组长度之和
// 求单词长度之和
Stream<String> stream = Stream.of("a", "bb", "ccc", "dddd");
int lengthSum = stream.reduce(0, // 初始值 // (1)
(sum, str) -> sum+str.length(), // 累加器 // (2)
(a, b) -> a+b); // 部分和拼接器,并行执行时才会用到 // (3)
// int lengthSum = stream.mapToInt(String::length).sum();
assertEquals(10, lengthSum);
3.6 收集(collect)
collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
collect主要依赖java.util.stream.Collectors类内置的静态方法。
3.6.1 归集(toList/toSet/toMap)
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。
下面用一个案例演示toList、toSet和toMap:
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toMap(Person::getName, p -> p));
System.out.println("toList:" + listNew);
System.out.println("toSet:" + set);
System.out.println("toMap:" + map);
}
}
运行结果:
toList:[6, 4, 6, 6, 20]
toSet:[4, 20, 6]
toMap:{Tom=mutest.Person@5fd0d5ae, Anni=mutest.Person@2d98a335}
3.6.2 统计(count/averaging)
Collectors提供了一系列用于数据统计的静态方法:
- 计数:count
- 平均值:averagingInt、averagingLong、averagingDouble
- 最值:maxBy、minBy
- 求和:summingInt、summingLong、summingDouble
- 统计以上所有:summarizingInt、summarizingLong、summarizingDouble
案例:统计员工人数、平均工资、工资总额、最高工资。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 求总数
Long count = personList.stream().collect(Collectors.counting());
// 求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
// 求最高工资
Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
// 求工资之和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
// 一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
System.out.println("员工总数:" + count);
System.out.println("员工平均工资:" + average);
System.out.println("员工工资总和:" + sum);
System.out.println("员工工资所有统计:" + collect);
}
}
运行结果:
员工总数:3
员工平均工资:7900.0
员工工资总和:23700
员工工资所有统计:DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}
3.6.3 分组(partitioningBy/groupingBy)
- 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
- 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
案例:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);
}
}
输出结果:
员工按薪资是否大于8000分组情况:{false=[mutest.Person@2d98a335, mutest.Person@16b98e56, mutest.Person@7ef20235], true=[mutest.Person@27d6c5e0, mutest.Person@4f3f5b24, mutest.Person@15aeb7ab]}
员工按性别分组情况:{female=[mutest.Person@16b98e56, mutest.Person@4f3f5b24, mutest.Person@7ef20235], male=[mutest.Person@27d6c5e0, mutest.Person@2d98a335, mutest.Person@15aeb7ab]}
员工按性别、地区:{female={New York=[mutest.Person@4f3f5b24, mutest.Person@7ef20235], Washington=[mutest.Person@16b98e56]}, male={New York=[mutest.Person@27d6c5e0, mutest.Person@15aeb7ab], Washington=[mutest.Person@2d98a335]}}
groupingBy 是能够根据字段进行分组,toMap 是把 List 的数据格式转化成 Map 的格式。
public void testGroupBy(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
Map<String, List<String>> strList = list.stream().collect(Collectors.groupingBy(s -> {
if("2".equals(s)) {
return "2";
}else {
return "1";
}
}));
}
3.6.4 接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
System.out.println("所有员工的姓名:" + names);
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
System.out.println("拼接后的字符串:" + string);
}
}
运行结果:
所有员工的姓名:Tom,Jack,Lily
拼接后的字符串:A-B-C
3.6.5 归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
System.out.println("员工扣税薪资总和:" + sum);
// stream的reduce
Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
System.out.println("员工薪资总和:" + sum2.get());
}
}
运行结果:
员工扣税薪资总和:8700
员工薪资总和:23700
3.7 排序(sorted)
sorted,中间操作。有两种排序:
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):Comparator排序器自定义排序
案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序
public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
.collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
if (p1.getSalary() == p2.getSalary()) {
return p2.getAge() - p1.getAge();
} else {
return p2.getSalary() - p1.getSalary();
}
}).map(Person::getName).collect(Collectors.toList());
System.out.println("按工资升序排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄升序排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}
}
运行结果:
按工资升序排序:[Lily, Tom, Sherry, Jack, Alisa]
按工资降序排序:[Sherry, Jack, Alisa, Tom, Lily]
先按工资再按年龄升序排序:[Lily, Tom, Sherry, Jack, Alisa]
先按工资再按年龄自定义降序排序:[Alisa, Jack, Sherry, Tom, Lily]
Sorted
Sorted 方法提供了排序的功能,并且允许我们自定义排序。
public void testSorted(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream()
.map(s -> Integer.valueOf(s))
// 等同于 .sorted(Comparator.naturalOrder()) 自然排序
.sorted()
.collect(Collectors.toList());
// 自定义排序器
list.stream()
.map(s -> Integer.valueOf(s))
// 反自然排序
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}
sorted()
默认按自然升序对集合进行排序,可使用Comparator提供 reverseOrder() 方法实现降序排列。
String[] arr = {"7","2","0","4","2","6"};
Stream.of(arr).sorted().forEach(e-> System.out.print(e+"->"));
System.out.println();
Stream.of(arr).sorted(Comparator.reverseOrder()).forEach(e-> System.out.print(e+"->"));
System.out.println();
Stream.of(arr).sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
}).forEach(e-> System.out.print(e+"->"));
//输出:
0->2->2->4->6->7->
7->6->4->2->2->0->
7->6->4->2->2->0->
3.8 提取/组合
流也可以进行合并、去重、限制、跳过等操作。
public class StreamTest {
public static void main(String[] args) {
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
}
}
运行结果:
流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7, 9, 11]
Distinct
distinct 方法有去重的功能:
public void testDistinct(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
list.stream()
.map(s -> Integer.valueOf(s))
.distinct()
.collect(Collectors.toList());
}
distinct()
将流中的元素去重。如果是自定义类,一定要重写equals()方法与hashCode()方法。
其它
Collect
将操作后的对象转化为新的对象:
public void testCollect(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
//转换为新的list
List newList = list.stream().map(s -> Integer.valueOf(s)).collect(Collectors.toList());
}
collect()
如果你发现某个功能在Stream接口中没找到,十有八九可以通过collect()方法实现。collect()是Stream接口方法中最灵活的一个,学会它才算真正入门Java函数式编程。
List<SaleOrderLineDO> newSaleOrderLines = saleOrderLines.stream()
.map(oldSaleOrderLine -> {
SaleOrderLineDO saleOrderLine = new SaleOrderLineDO();
saleOrderLine.setId(oldSaleOrderLine.getId());
saleOrderLine.setBuyerId(oldSaleOrderLine.getBuyerId());
saleOrderLine.setReceiveQuantity(oldSaleOrderLine.getQuantity());
saleOrderLine.setGmtModified(new Date());
saleOrderLine.setModifier(modifier);
return saleOrderLine;
}).collect(Collectors.toList());
Map<Long, Integer> purchaseSendQuantityMap = realQuantities.stream()
.collect(Collectors.toMap(RealQuantity::getSubLineId, RealQuantity::getSendQuantity));
Map
map 方法可以让我们进行一些流的转化,比如原来流中的元素是 A,通过 map 操作,可以使返回的流中的元素是 B。
public void testMap() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
//通过 map 方法list中元素转化成 小写
List<String> strLowerList = list.stream()
.map(str -> str.toLowerCase())
.collect(Collectors.toList());
}
MapToInt
mapToInt 方法的功能和 map 方法一样,只不过 mapToInt 返回的结果已经没有泛型,已经明确是 int 类型的流了。
源码如下:
public void testMapToInt() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream()
.mapToInt(s->Integer.valueOf(s))
// 一定要有 mapToObj,因为 mapToInt 返回的是 IntStream,因为已经确定是 int 类型了
// 所有没有泛型的,而 Collectors.toList() 强制要求有泛型的流,所以需要使用 mapToObj
// 方法返回有泛型的流
.mapToObj(s->s)
.collect(Collectors.toList());
list.stream()
.mapToDouble(s->Double.valueOf(s))
// DoubleStream/IntStream 有许多 sum(求和)、min(求最小值)、max(求最大值)、average(求平均值)等方法
.sum();
}
FindFirst
findFirst 表示匹配到第一个满足条件的值就返回:
public void testFindFirst(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
list.stream()
.filter(s->"2".equals(s))
.findFirst()
.get();
// 防止空指针
list.stream()
.filter(s->"2".equals(s))
.findFirst()
// orElse 表示如果 findFirst 返回 null 的话,就返回 orElse 里的内容
.orElse("3");
Optional<String> str= list.stream()
.filter(s->"2".equals(s))
.findFirst();
// isPresent 为 true 的话,表示 value != null
if(str.isPresent()){
return;
}
}
Peek
peek 方法很简单,我们在 peek 方法里面做任意没有返回值的事情,比如打印日志:
public void testPeek(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream().map(s -> Integer.valueOf(s))
.peek(s -> System.out.println(s))
.collect(Collectors.toList());
}
peek()
将流中的元素进行消费,不改变流中的元素。
peek()与map()的区别:map操作可以改变流中的元素,有返回值;peek操作仅仅在操作中消费元素,没有返回值,传入下一个操作的元素不会改变。
String[] arr = {"1","2","3","4","5","6"};
Stream.of(arr).map(e-> {
e = e + "a";
System.out.println("map:"+e);
return e;
}).forEach(e-> System.out.println("map-foreach:"+e));
Stream.of(arr).peek(e-> {
e = e + "a";
System.out.println("peek:"+e);
}).forEach(e-> System.out.println("peek-foreach:"+e));
//输出:
map:1a
map-foreach:1a
map:2a
map-foreach:2a
map:3a
map-foreach:3a
map:4a
map-foreach:4a
map:5a
map-foreach:5a
map:6a
map-foreach:6a
peek:1a
peek-foreach:1
peek:2a
peek-foreach:2
peek:3a
peek-foreach:3
peek:4a
peek-foreach:4
peek:5a
peek-foreach:5
peek:6a
peek-foreach:6
unordered()
基于调用流,返回一个无序流。
在有序流的并行执行情况下,保持 的顺序性是需要高昂的缓冲开销。所以在处理元素时,不需要保证元素的顺序性,那么我们可以使用 unordered() 方法来实现无序流。
Limit
limit 方法会限制输出值个数,入参是限制的个数大小:
public void testLimit(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream()
.map(s -> Integer.valueOf(s))
.limit(2L)
.collect(Collectors.toList());
}
limit()
skip()
跳过前面n个元素,返回新的stream流。
Max,Min
通过 max、min 方法,可以获取集合中最大、最小的对象。
public void testMaxMin(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
list.stream().max(Comparator.comparing(s -> Integer.valueOf(s))).get();
list.stream().min(Comparator.comparing(s -> Integer.valueOf(s))).get();
}
stream优秀示例
下面,我们看看java8将函数式接口封装到流中如何高效的帮助我们处理集合。
本篇所有示例都基于以下三个类。OutstandingClass:班级;Student:学生;SpecialityEnum:特长。
惰性求值与及早求值
惰性求值:只描述Stream,操作的结果也是Stream,这样的操作称为惰性求值。惰性求值可以像建造者模式一样链式使用,最后再使用及早求值得到最终结果。
及早求值:得到最终的结果而不是Stream,这样的操作称为及早求值。
2、常用的流
2.1 collect(Collectors.toList())
将流转换为list。还有toSet(),toMap()等。及早求值。
public class TestCase {
public static void main(String[] args) {
List<Student> studentList = Stream.of(new Student("路飞", 22, 175),
new Student("红发", 40, 180),
new Student("白胡子", 50, 185)).collect(Collectors.toList());
System.out.println(studentList);
}
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null},
//Student{name='红发', age=40, stature=180, specialities=null},
//Student{name='白胡子', age=50, stature=185, specialities=null}]
2.2 filter
顾名思义,起过滤筛选的作用。内部就是Predicate接口。惰性求值。
比如我们筛选出出身高小于180的同学。
public class TestCase {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
List<Student> list = students.stream()
.filter(stu -> stu.getStature() < 180)
.collect(Collectors.toList());
System.out.println(list);
}
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null}]
2.3 map
转换功能,内部就是Function接口。惰性求值
public class TestCase {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
List<String> names = students.stream().map(student -> student.getName())
.collect(Collectors.toList());
System.out.println(names);
}
}
//输出结果
//[路飞, 红发, 白胡子]
例子中将student对象转换为String对象,获取student的名字。
2.4 flatMap
将多个Stream合并为一个Stream。惰性求值
public class TestCase {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
List<Student> studentList = Stream.of(students,
asList(new Student("艾斯", 25, 183),
new Student("雷利", 48, 176)))
.flatMap(students1 -> students1.stream()).collect(Collectors.toList());
System.out.println(studentList);
}
}
//输出结果
//[Student{name='路飞', age=22, stature=175, specialities=null},
//Student{name='红发', age=40, stature=180, specialities=null},
//Student{name='白胡子', age=50, stature=185, specialities=null},
//Student{name='艾斯', age=25, stature=183, specialities=null},
//Student{name='雷利', age=48, stature=176, specialities=null}]
调用Stream.of的静态方法将两个list转换为Stream,再通过flatMap将两个流合并为一个。
2.5 max和min
我们经常会在集合中求最大或最小值,使用流就很方便。及早求值。
public class TestCase {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
Optional<Student> max = students.stream()
.max(Comparator.comparing(stu -> stu.getAge()));
Optional<Student> min = students.stream()
.min(Comparator.comparing(stu -> stu.getAge()));
//判断是否有值
if (max.isPresent()) {
System.out.println(max.get());
}
if (min.isPresent()) {
System.out.println(min.get());
}
}
}
//输出结果
//Student{name='白胡子', age=50, stature=185, specialities=null}
//Student{name='路飞', age=22, stature=175, specialities=null}
max、min接收一个Comparator(例子中使用java8自带的静态函数,只需要传进需要比较值即可。)并且返回一个Optional对象,该对象是java8新增的类,专门为了防止null引发的空指针异常。
可以使用max.isPresent()判断是否有值;可以使用max.orElse(new Student()),当值为null时就使用给定值;也可以使用max.orElseGet(() -> new Student());这需要传入一个Supplier的lambda表达式。
2.6 count
统计功能,一般都是结合filter使用,因为先筛选出我们需要的再统计即可。及早求值
public class TestCase {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
long count = students.stream().filter(s1 -> s1.getAge() < 45).count();
System.out.println("年龄小于45岁的人数是:" + count);
}
}
//输出结果
//年龄小于45岁的人数是:2
2.7 reduce
reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count 、 min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。及早求值。
public class TestCase {
public static void main(String[] args) {
Integer reduce = Stream.of(1, 2, 3, 4).reduce(0, (acc, x) -> acc+ x);
System.out.println(reduce);
}
}
//输出结果
//10
我们看得reduce接收了一个初始值为0的累加器,依次取出值与累加器相加,最后累加器的值就是最终的结果。
高级集合类及收集器
3.1 转换成值
收集器,一种通用的、从流生成复杂值的结构。只要将它传给 collect 方法,所有的流就都可以使用它了。标准类库已经提供了一些有用的收集器,以下示例代码中的收集器都是从 java.util.stream.Collectors 类中静态导入的。
public class CollectorsTest {
public static void main(String[] args) {
List<Student> students1 = new ArrayList<>(3);
students1.add(new Student("路飞", 23, 175));
students1.add(new Student("红发", 40, 180));
students1.add(new Student("白胡子", 50, 185));
OutstandingClass ostClass1 = new OutstandingClass("一班", students1);
//复制students1,并移除一个学生
List<Student> students2 = new ArrayList<>(students1);
students2.remove(1);
OutstandingClass ostClass2 = new OutstandingClass("二班", students2);
//将ostClass1、ostClass2转换为Stream
Stream<OutstandingClass> classStream = Stream.of(ostClass1, ostClass2);
OutstandingClass outstandingClass = biggestGroup(classStream);
System.out.println("人数最多的班级是:" + outstandingClass.getName());
System.out.println("一班平均年龄是:" + averageNumberOfStudent(students1));
}
/**
* 获取人数最多的班级
*/
private static OutstandingClass biggestGroup(Stream<OutstandingClass> outstandingClasses) {
return outstandingClasses.collect(
maxBy(comparing(ostClass -> ostClass.getStudents().size())))
.orElseGet(OutstandingClass::new);
}
/**
* 计算平均年龄
*/
private static double averageNumberOfStudent(List<Student> students) {
return students.stream().collect(averagingInt(Student::getAge));
}
}
//输出结果
//人数最多的班级是:一班
//一班平均年龄是:37.666666666666664
3.2 转换成块
常用的流操作是将其分解成两个集合,Collectors.partitioningBy帮我们实现了,接收一个Predicate函数式接口。
将示例学生分为会唱歌与不会唱歌的两个集合。
public class PartitioningByTest {
public static void main(String[] args) {
//省略List<student> students的初始化
Map<Boolean, List<Student>> listMap = students.stream().collect(
Collectors.partitioningBy(student -> student.getSpecialities().
contains(SpecialityEnum.SING)));
}
}
3.3 数据分组
数据分组是一种更自然的分割数据操作,与将数据分成 ture 和 false 两部分不同,可以使用任意值对数据分组。Collectors.groupingBy接收一个Function做转换。
如图,我们使用groupingBy将根据进行分组为圆形一组,三角形一组,正方形一组。
例子:根据学生第一个特长进行分组
public class GroupingByTest {
public static void main(String[] args) {
//省略List<student> students的初始化
Map<SpecialityEnum, List<Student>> listMap =
students.stream().collect(
Collectors.groupingBy(student -> student.getSpecialities().get(0)));
}
}
Collectors.groupingBy与SQL 中的 group by 操作是一样的。
3.4 字符串拼接
如果将所有学生的名字拼接起来,怎么做呢?通常只能创建一个StringBuilder,循环拼接。使用Stream,使用Collectors.joining()简单容易。
public class JoiningTest {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
String names = students.stream()
.map(Student::getName).collect(Collectors.joining(",","[","]"));
System.out.println(names);
}
}
//输出结果
//[路飞,红发,白胡子]
joining接收三个参数,第一个是分界符,第二个是前缀符,第三个是结束符。也可以不传入参数Collectors.joining(),这样就是直接拼接。
四、总结
本篇主要从实际使用讲述了常用的方法及流,使用java8可以很清晰表达你要做什么,代码也很简洁。本篇例子主要是为了讲解较为简单,大家可以去使用java8重构自己现有的代码,自行领会lambda的奥妙。
本文说的Stream要组合使用才会发挥更大的功能,链式调用很迷人,根据自己的业务去做吧。
原理
Stream Pipelines
前面我们也体验了Stream的简洁、简单、简约的代码风格。那么到底Stream的底层是如何实现的呢?下面我们用一个简单的例子来一步一步的理解下。一个栗子:从一个字符串列表中找到以‘a’开头的、最长的字符串长度。
一种简单的方式是每一次函数调用都执行一次迭代,这样并将处理中间结果放到某种数据结构中(比如数组,容器等)。
List<String> strings = Arrays.asList("a", "bb", "ccc", "abcd");
List<String> startWithAList = new ArrayList<>();
for (String string: strings) {
if(string.startsWith("a")){
startWithAList.add(string); // 1. filter(), 保留以A开头的字符串
}
}
List<Integer> lengths = new ArrayList<>();
for (String string : startWithAList) {
lengths.add(string.length()); // 2. mapToInt(), 转换成长度
}
int maxLength = 0;
for(Integer length : lengths){
maxLength = Math.max(length, maxLength); // 3. max(), 保留最长的长度
}
assertEquals(4, maxLength);
具体说来,就是调用filter()方法后立即执行,选出所有以A开头的字符串并放到一个列表list1中,之后让list1传递给mapToInt()方法并立即执行,生成的结果放到list2中,最后遍历list2找出最大的数字作为最终结果。程序的执行流程如如所示:虽然这样做实现起来非常简单直观,但有两个明显的弊端:
1. 迭代次数多。迭代次数跟函数调用的次数相等;
2. 频繁产生中间结果。每次函数调用都产生一次中间结果,存储开销无法接受;
这些弊端使得效率低下,根本无法接受。如果不使用以上以上方式,我们都知道上述需求该如何在一次迭代中完成,大致是如下形式:
int maxLength = 0;
List<String> strings = Arrays.asList("a", "bb", "ccc", "abcd");
for(String str : strings){
if(str.startsWith("a")){ // 1. filter(), 保留以A开头的字符串
int len = str.length(); // 2. mapToInt(), 转换成长度
maxLength = Math.max(len, maxLength); // 3. max(), 保留最长的长度
}
}
assertEquals(4, maxLength);
采用这种方式我们不但减少了迭代次数,也避免了存储中间结果,显然这就是流水线,因为我们把三个操作放在了一次迭代当中。只要我们事先知道用户意图,总是能够采用上述方式实现跟Stream API等价的功能,但问题是Stream类库的设计者并不知道用户的意图是什么。如何在无法假设用户行为的前提下实现流水线,是类库的设计者要考虑的问题。采用Stream的方式的代码如下。
int maxLength = Stream.of("a", "bb", "ccc", "abcd")
.filter(value -> value.startsWith("a"))
.mapToInt(String::length)
.max()
.orElse(0);
assertEquals(4, maxLength);
Stream流水线解决方案
我们大致能够想到,应该采用某种方式记录用户每一步的操作,当用户调用结束操作时将之前记录的操作叠加到一起在一次迭代中全部执行掉。沿着这个思路,有几个问题需要解决:
- 用户的操作如何记录?
- 操作如何叠加?
- 叠加之后的操作如何执行?
- 执行后的结果(如果有)在哪里?
以下代码为例:Stream.of("onE", "twO", "threE", "fouR")
// .parallel()
.filter(e -> e.length() > 3)
.map(String::toLowerCase)
.sorted()
.map(String::toUpperCase)
.forEach(System.out::println);
操作如何记录?
注意这里使用的是“操作(operation)”一词,指的是“Stream中间操作”的操作,很多Stream操作会需要一个回调函数(Lambda表达式),因此一个完整的操作是<数据来源,操作,回调函数>构成的三元组。Stream中使用Stage的概念来描述一个完整的操作,并用某种实例化后的PipelineHelper来代表Stage,将具有先后顺序的各个Stage连到一起,就构成了整个流水线。跟Stream相关类和接口的继承关系图示。
由于篇幅原因,DoublePipeline没在图中展示,DoublePipeline、IntPipeline、LongPipeline这三个类专门为三种基本类型而定制的,跟ReferencePipeline是并列关系。图中Head用于表示第一个Stage,即调用调用诸如Collection.stream()方法产生的Stage,很显然这个Stage里不包含任何操作;StatelessOp和StatefulOp分别表示无状态和有状态的Stage,对应于无状态和有状态的中间操作。
Stream流水线组织结构示意图如下:图中通过Stream.of()方法得到Head Stage,紧接着调用一系列的中间操作,不断产生新的Stream。这些Stream对象以双向链表的形式组织在一起,构成整个流水线,由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。这就是Stream记录操作的方式。
对于AbstractPipeline,有两个构造函数,分别生成head、intermediate的pipeline stage。
/**
* 构造函数:生成Pipeline的头
*
* @param source {@code Spliterator} describing the stream source
* @param sourceFlags the source flags for the stream source, described in
* {@link StreamOpFlag}
* @param parallel {@code true} if the pipeline is parallel
*/
AbstractPipeline(Spliterator<?> source,
int sourceFlags, boolean parallel) {
this.previousStage = null;
this.sourceSpliterator = source;
this.sourceStage = this;
this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
// The following is an optimization of:
// StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
this.depth = 0;
this.parallel = parallel;
}
/**
* 构造函数:将中间stage连接到当前pipeline尾部
*
* @param previousStage the upstream pipeline stage
* @param opFlags the operation flags for the new stage, described in
* {@link StreamOpFlag}
*/
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
if (previousStage.linkedOrConsumed)
thrownew IllegalStateException(MSG_STREAM_LINKED);
previousStage.linkedOrConsumed = true;
previousStage.nextStage = this;
this.previousStage = previousStage;
this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
this.sourceStage = previousStage.sourceStage;
if (opIsStateful())
sourceStage.sourceAnyStateful = true;
this.depth = previousStage.depth + 1;
}
操作如何叠加?
以上只是解决了操作记录的问题,要想让流水线起到应有的作用我们需要一种将所有操作叠加到一起的方案。你可能会觉得这很简单,只需要从流水线的head开始依次执行每一步的操作(包括回调函数)就行了。这听起来似乎是可行的,但是你忽略了前面的Stage并不知道后面Stage到底执行了哪种操作,以及回调函数是哪种形式。换句话说,只有当前Stage本身才知道该如何执行自己包含的动作。这就需要有某种协议来协调相邻Stage之间的调用关系。
这种协议由Sink接口完成,Sink接口包含的方法如下表所示:
方法名 | 作用 |
---|---|
void begin(long size) | 开始遍历元素之前调用该方法,通知Sink做好准备。 |
void end() | 所有元素遍历完成之后调用,通知Sink没有更多的元素了。 |
boolean cancellationRequested() | 是否可以结束操作,可以让短路操作尽早结束。 |
void accept(T t) | 遍历元素时调用,接受一个待处理元素,并对元素进行处理。Stage把自己包含的操作和回调方法封装到该方法里,前一个Stage只需要调用当前Stage.accept(T t)方法就行了。 |
有了上面的协议,相邻Stage之间调用就很方便了,每个Stage都会将自己的操作封装到一个Sink里,前一个Stage只需调用后一个Stage的accept()方法即可,并不需要知道其内部是如何处理的。当然对于有状态的操作,Sink的begin()和end()方法也是必须实现的。比如Stream.sorted()是一个有状态的中间操作,其对应的Sink.begin()方法可能创建一个乘放结果的容器,而accept()方法负责将元素添加到该容器,最后end()负责对容器进行排序。对于短路操作,Sink.cancellationRequested()也是必须实现的,比如Stream.findFirst()是短路操作,只要找到一个元素,cancellationRequested()就应该返回true,以便调用者尽快结束查找。Sink的四个接口方法常常相互协作,共同完成计算任务。实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接口方法。
有了Sink对操作的包装,Stage之间的调用问题就解决了,执行时只需要从流水线的head开始对数据源依次调用每个Stage对应的Sink.{begin(), accept(), cancellationRequested(), end()}方法就可以了。一种可能的Sink.accept()方法流程是这样的:
void accept(U u){
1. 使用当前Sink包装的回调函数处理u
2. 将处理结果传递给流水线下游的Sink
}
Sink接口的其他几个方法也是按照这种[处理->转发]的模型实现。下面我们结合具体例子看看Stream的中间操作是如何将自身的操作包装成Sink以及Sink是如何将处理结果转发给下一个Sink的。先看Stream.map()方法:
// java.util.stream.ReferencePipeline#map
// 调用该方法将产生一个新的Stream
publicfinal <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
returnnew StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override/*opWrapSink()方法返回由回调函数包装而成Sink,由wrapSink()触发*/
Sink<P_OUT> opWrapSink(int flags, Sink<R> downstream) {
returnnew Sink.ChainedReference<P_OUT, R>(downstream) {
@Override
public void accept(P_OUT u) {
R r = mapper.apply(u); // 1. 使用当前Sink包装的回调函数mapper处理u
downstream.accept(r); // 2. 将处理结果传递给流水线下游的Sink
}
};
}
};
}
上述代码看似复杂,其实逻辑很简单,就是将回调函数mapper包装到一个Sink当中。由于Stream.map()是一个无状态的中间操作,所以map()方法返回了一个StatelessOp内部类对象(一个新的Stream),调用这个新Stream的opWrapSink()方法将得到一个包装了当前回调函数的Sink。
// java.util.stream.Sink.ChainedReference: 244行
staticabstractclass ChainedReference<T, E_OUT> implements Sink<T> {
protectedfinal Sink<? super E_OUT> downstream;
public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}
@Override
public void begin(long size) {
downstream.begin(size);
}
@Override
public void end() {
downstream.end();
}
@Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}
再来看一个复杂一点的例子。Stream.sorted()方法将对Stream中的元素进行排序,显然这是一个有状态的中间操作,因为读取所有元素之前是没法得到最终顺序的。抛开模板代码直接进入问题本质,sorted()方法是如何将操作封装成Sink的呢?sorted()一种可能封装的Sink代码如下:
// java.util.stream.SortedOps.RefSortingSink:371行
// Stream.sorted()方法用到的Sink实现,由java.util.stream.SortedOps.OfRef#opWrapSink:133行 触发
class RefSortingSink<T> extends AbstractRefSortingSink<T> {
private ArrayList<T> list;// 存放用于排序的元素
RefSortingSink(Sink<? super T> downstream, Comparator<? super T> comparator) {
super(downstream, comparator);
}
@Override
public void begin(long size) {
...
// 1. 创建一个存放排序元素的列表
list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
@Override
public void end() {
list.sort(comparator);// 3. 只有元素全部接收之后才能开始排序
downstream.begin(list.size());
if (!cancellationWasRequested) {// 下游Sink不包含短路操作
list.forEach(downstream::accept);// 将处理结果传递给流水线下游的Sink
}
else {// 4. 下游Sink包含短路操作
for (T t : list) {// 每次都调用cancellationRequested()询问是否可以结束处理。
if (downstream.cancellationRequested()) break;
downstream.accept(t);// 将处理结果传递给流水线下游的Sink
}
}
downstream.end();
list = null;
}
@Override
public void accept(T t) {
list.add(t);// 2. 使用当前Sink包装动作处理t,只是简单的将元素添加到中间列表当中
}
}
上述代码完美的展现了Sink的四个接口方法是如何协同工作的:首先beging()方法告诉Sink参与排序的元素个数,方便确定中间结果容器的的大小;
之后通过accept()方法将元素添加到中间结果当中,最终执行时调用者会不断调用该方法,直到遍历所有元素;
最后end()方法告诉Sink所有元素遍历完毕,启动排序步骤,排序完成后将结果传递给下游的Sink;
如果下游的Sink是短路操作,将结果传递给下游时不断询问下游cancellationRequested()是否可以结束处理。
下图为Sink的构建图
叠加之后的操作如何执行?
Sink完美封装了Stream每一步操作,并给出了[处理->转发]的模式来叠加操作。这一连串的齿轮已经咬合,就差最后一步拨动齿轮启动执行。是什么启动这一连串的操作呢?也许你已经想到了启动的原始动力就是结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。
结束操作之后不能再有别的操作,所以结束操作不会创建新的流水线阶段(Stage),直观的说就是流水线的链表不会在往后延伸了。结束操作会创建一个包装了自己操作的Sink,这也是流水线中最后一个Sink,这个Sink只需要处理数据而不需要将结果传递给下游的Sink(因为没有下游)。对于Sink的[处理->转发]模型,结束操作的Sink就是调用链的出口。
我们再来考察一下上游的Sink是如何找到下游Sink的。一种可选的方案是在PipelineHelper中设置一个Sink字段,在流水线中找到下游Stage并访问Sink字段即可。但Stream类库的设计者没有这么做,而是设置了一个Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法来得到Sink,该方法的作用是返回一个新的包含了当前Stage代表的操作以及能够将结果传递给downstream的Sink对象。为什么要产生一个新对象而不是返回一个Sink字段?这是因为使用opWrapSink()可以将当前操作与下游Sink(上文中的downstream参数)结合成新Sink。试想只要从流水线的最后一个Stage开始,不断调用上一个Stage的opWrapSink()方法直到最开始(不包括stage0,因为stage0代表数据源,不包含操作),就可以得到一个代表了流水线上所有操作的Sink,用代码表示就是这样:
// java.util.stream.AbstractPipeline#wrapSink:513行
// 从下游向上游不断包装Sink。
// 如果最初传入的Sink代表结束操作,函数返回时就可以得到一个代表了流水线上所有操作的Sink。
@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
Objects.requireNonNull(sink);
for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink<P_IN>) sink;
}
现在流水线上从开始到结束的所有的操作都被包装到了一个Sink里,执行这个Sink就相当于执行整个流水线,执行Sink的代码如下:
// java.util.stream.AbstractPipeline#copyInto:476行
// 对spliterator代表的数据执行wrappedSink代表的操作
@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
Objects.requireNonNull(wrappedSink);
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知开始遍历
spliterator.forEachRemaining(wrappedSink);// 迭代
wrappedSink.end();// 通知遍历结束
}
else {
copyIntoWithCancel(wrappedSink, spliterator);
}
}
上述代码首先调用wrappedSink.begin()方法告诉Sink数据即将到来,然后调用spliterator.forEachRemaining()方法对数据进行迭代(Spliterator是容器的一种迭代器),最后调用wrappedSink.end()方法通知Sink数据处理结束。
执行后的结果在哪里?
最后一个问题是流水线上所有操作都执行后,用户所需要的结果(如果有)在哪里?首先要说明的是不是所有的Stream结束操作都需要返回结果,有些操作只是为了使用其副作用(Side-effects),比如使用Stream.forEach()方法将结果打印出来就是常见的使用副作用的场景(事实上,除了打印之外其他场景都应避免使用副作用),对于真正需要返回结果的结束操作结果存在哪里呢?
特别说明:副作用不应该被滥用,也许你会觉得在Stream.forEach()里进行元素收集是个不错的选择,就像下面代码中那样,但遗憾的是这样使用的正确性和效率都无法保证,因为Stream可能会并行执行。大多数使用副作用的地方都可以使用归约操作更安全和有效的完成。
// 错误的收集方式
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
.forEach(s -> results.add(s)); // Unnecessary use of side-effects!
// 正确的收集方式
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList()); // No side-effects!
回到流水线执行结果的问题上来,需要返回结果的流水线结果存在哪里呢?这要分不同的情况讨论,下表给出了各种有返回结果的Stream结束操作。
返回类型 | 对应的结束操作 |
---|---|
boolean | anyMatch() allMatch() noneMatch() |
Optional | findFirst() findAny() |
归约结果 | reduce() collect() |
数组 | toArray() |
对于表中返回boolean或者Optional的操作(Optional是存放一个值的容器)的操作,由于值返回一个值,只需要在对应的Sink中记录这个值,等到执行结束时返回就可以了。
对于归约操作,最终结果放在用户调用时指定的容器中(容器类型通过收集器指定)。collect(), reduce(), max(), min()都是归约操作,虽然max()和min()也是返回一个Optional,但事实上底层是通过调用reduce()方法实现的。
对于返回是数组的情况,毫无疑问的结果会放在数组当中。这么说当然是对的,但在最终返回数组之前,结果其实是存储在一种叫做Node的数据结构中的。Node是一种多叉树结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素。这样做是为了并行执行方便。
对于collect这样的操作是需要拿到最终end产生的结果.end产生的结果在最后一个Sink中,这样的操作最终都会提供一个取出数据的get方法.
// java.util.stream.AbstractPipeline#evaluate(java.util.stream.TerminalOp<E_OUT,R>) 226行
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert getOutputShape() == terminalOp.inputShape();
if (linkedOrConsumed)
thrownew IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;
return isParallel()
? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
: terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}
// java.util.stream.ReduceOps.ReduceOp:684行
privatestaticabstractclass ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
implements TerminalOp<T, R> {
@Override
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}
@Override
public <P_IN> R evaluateParallel(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
returnnew ReduceTask<>(this, helper, spliterator).invoke().get();
}
}
其它
常用操作
创建集合
List<String> strList = Stream.of("t1","t2","t3").collect(Collectors.toList());
LambdaQueryWrapper<Task> queryWrapper = Wrappers.<Task>lambdaQuery().eq(Task::getSite, "新华网")
.eq(Task::getStatus, 0);
mybatis查询构建
�java.util.List<T> list(com.baomidou.mybatisplus.core.conditions.Wrapper<T> queryWrapper)
Page
Page<BlogDO> page = blogRepository.findPublicBlogPage(pageable);
List<String> userIds = page.getContent().parallelStream().map(BlogDO::getUserId).distinct().collect(Collectors.toList());
Page<BlogDO> page = blogRepository.findPublicBlogPage(pageable);
List<String> userIds = page.getContent().parallelStream().map(BlogDO::getUserId).distinct().collect(Collectors.toList());
Page<BlogDO> page = blogRepository.findUserPublicBlogPage(bloggerId, pageable);
List<BlogDTO> blogDTOList = page.getContent().stream().map(blogDO -> blogMapper.toDto(blogDO)).collect(Collectors.toList());
String teamName = team.stream().map(String::valueOf).collect(Collectors.joining(","));
List<Map<String, Object>> list =
aipApiresUseAppRepository.findApiApplyDataList(userId, apiName);
list.stream().forEach(resMap ->
{
Map<String, Object> map = new HashMap<>();
map.put("apiFunNo", resMap.get("apiFunNo"));
map.put("apiResCode", resMap.get("apiResCode"));
map.put("apiUrl", resMap.get("apiUrl"));
map.put("appDate", resMap.get("appDate"));
map.put("applyExpirTime", resMap.get("applyExpirTime"));
map.put("title", resMap.get("title"));
map.put("userId", resMap.get("userId"));
map.put("approveStatus", resMap.get("approveStatus"));
map.put("icon", ResourceTypeEnum.getImgUrlByCode(
Utils.formatStr(resMap.get("origDataResourceType"))));
resList.add(map);
});
List<Map<String, Object>> content = commentPage.getContent();
Map<String, Map<String, Object>> userInfoMap = content.stream().map(map -> ((String) map.get("user_id")))
.distinct()
.map(userId -> queryFeginUserInfo(userId))
.collect(Collectors.toMap(map -> ((String) map.get("userId")), map -> map));
List<AuthorityDO> sysRoles = null;
List<AuthorityDO> collectDepts = sysRoles.stream().peek(sysRole ->
sysRole.setRoleDepts(roleDeptRepository.findByRoleId(sysRole.getId()).stream().map(RoleDeptDO::getDeptId).collect(Collectors.toList()))
).collect(Collectors.toList());
从IPage 中取值和赋值
IPage
// 是baomidou.mybatisplus 中带的分页
//pom 依赖如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.2</version>
</dependency>
实际开发中的代码为例
//从数据库中获取数据,使用分页对象接口
IPage<IaManagementRightListVo> list = iaShopManagementRightMapper.getManagementRightList(page,query);
//转为list进行遍历数据
List<IaManagementRightListVo> lis = list.getRecords();
for (int i = 0; i < lis.size(); i++) {
//去掉小数点后两位
String pricePreMu = lis.get(i).getPricePreMu();
String [] str = pricePreMu.split("[.]");
System.out.println(str[0]); //结果
//将价格进行千分位算法 NumberUtils 工具类
BigDecimal num = NumberUtils.createBigDecimal(str[0]);
lis.get(i).setPricePreMu(ConvertUpMoney.micrometer(num));
//将计算好的值存入list返回给Ipage
list.setRecords(lis);
}
List遍历
JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
return jsonArray.stream().map(data -> {
JSONObject json = (JSONObject) data;
}).collect(Collectors.toList());
对象创建
Task.builder()
.site("")
.title(json.getStr("Title"))
.url(json.getStr("LinkUrl"))
.coverImg(json.getStr("PicLinks"))
.pubDate(json.getLocalDateTime("PubTime", LocalDateTime.now()))
.status(0)
.build();
Stream流使用之去除List集合中的某个值
/**
* @Description: 使用Stream流去除集合中的某个值
* @author: 小程
* @date: 2022/3/26 11:45
* @Return: void
*/
@Test
public void Test3(){
// 创建一个集合
List<String> list = new ArrayList<>();
// 往集合里添加元素
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
list.add("1");
list.add("1");
list.add("1");
// 除去集合中为1的元素
List<String> collect = list.stream().filter(f -> !f.equals("1")).collect(Collectors.toList());
System.out.println("执行结果为:");
collect.forEach(c->{
System.out.println(c);
});
}
执行结果
2
3
4
5
6
7