一、Stream介绍

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

二、Stream创建

2.1 使用集合对象的stream方法构建流

1
2
3
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C", "C++", "C#", "Golang", "Swift");
s = list.stream();
System.out.println(s.count()); //9

2.2 数值范围构建

IntStream和LongStream对象支持range和rangeClosed方法来构建数值流。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
比如对10到20的整数求和:

1
2
3
4
        System.out.println(IntStream.range(10,20).sum()); //145
System.out.println(LongStream.range(10,20).sum()); //145
System.out.println(IntStream.rangeClosed(10,20).sum()); //165
System.out.println(LongStream.rangeClosed(10,20).sum()); //165

2.3 由值构建

静态方法Stream.of可以显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流:

1
2
Stream<String> s = Stream.of("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
System.out.println(s.count()); //7

也可以使用Stream.empty()构建一个空流:

1
2
Stream<Object> o = Stream.empty();
System.out.println(o.count()); //0

2.4 由数组构建

静态方法Arrays.stream可以通过数组创建一个流。它接受一个数组作为参数。

1
2
3
int[] arr = {1, 2, 3, 4, 5, 6, 7};
IntStream intStream = Arrays.stream(arr);
System.out.println(intStream.count()); //7

2.5 由文件生成流

java.nio.file.Files中的很多静态方法都会返回一个流。例如Files.lines方法会返回一个由指定文件中的各行构成的字符串流。比如统计一个文件中共有多少个字:

1
2
3
4
5
6
7
8
9
10
        long wordCout = 0L;
//Stream<String> lines;
try (Stream<String> lines = Files.lines(Paths.get("E:/stream/stream.txt"), Charset.defaultCharset())) {
wordCout = lines.map(l -> l.split(""))
.flatMap(Arrays::stream)
.count();
} catch (Exception ignore) {
ignore.printStackTrace();
}
System.out.println(wordCout);

2.6 由函数构造

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流。比如下面的例子构建了10个偶数:

1
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);

iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元 素加上2。因此,iterate方法生成了一个所有正偶数的流:流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。

与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数,比如下面的例子生成了5个0到1之间的随机双精度数:

1
Stream.generate(Math::random).limit(5).forEach(System.out::println);

0.23353383579369869
0.4108268031623806
0.4942831052728862
0.972680756534086
0.8616162073298483

三、流的操作介绍

流的使用一般包括三件事情:

一个数据源(如集合)来执行一个查询;

一个中间操作链,形成一条流的流水线;

一个终端操作,执行流水线,并能生成结果。

下表列出了流中常见的中间操作和终端操作:

操作类型返回类型使用的类型/函数式接口函数描述符
filter中间StreamPredicateT -> boolean
distinct中间Stream
skip中间Streamlong
limit中间Streamlong
map中间StreamFunction<T, R>T -> R
flatMap中间StreamFunction<T, Stream>T -> Stream
sorted中间StreamComparator(T, T) -> int
anyMatch终端booleanPredicateT -> boolean
noneMatch终端booleanPredicateT -> boolean
allMatch终端booleanPredicateT -> boolean
findAny终端Optional
findFirst终端Optional
forEach终端voidConsumerT -> void
collect终端RCollector<T, A, R>
reduce终端OptionalBinaryOperator(T, T) -> T
count终端long

四、中间操作

下面的操作都会用到这个基础数据:

1
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C", "C++", "C++", "C#", "Golang", "Swift");

4.1 filter

Streams接口支持·filter方法,该方法接收一个Predicate,函数描述符为T -> boolean,用于对集合进行筛选,返回所有满足的元素:

1
list.stream().filter(str -> str.contains("C")).forEach(System.out::println);

结果输出:

C
C++
C++
C#

4.2 distinct

distinct方法用于排除流中重复的元素,类似于SQL中的distinct操作。比如筛选中集合中所有符合条件的数据,并排除重复的结果:

1
list.stream().filter(str -> str.contains("C")).distinct().forEach(System.out::println);

结果输出:

C
C++
C#

4.3 skip

skip(n)方法用于跳过流中的前n个元素,如果集合元素小于n,则返回空流。比如筛选出包含C的元素,并排除前两个:

1
list.stream().filter(str -> str.contains("C")).skip(2).forEach(System.out::println);

结果输出:

C++
C#

4.4 limit

limit(n)方法返回一个长度不超过n的流

1
list.stream().filter(str -> str.contains("C")).limit(3).forEach(System.out::println);

结果输出:

C
C++
C++

4.5 map

map方法接收一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。如:

1
list.stream().map(String::toLowerCase).forEach(System.out::println);

结果输出:

java javascript python php c c++ c++ c# golang swift

map还支持将流特化为指定原始类型的流,如通过mapToInt,mapToDouble和mapToLong方法,可以将流转换为IntStream,DoubleStream和LongStream。特化后的流支持sum,min和max方法来对流中的元素进行计算。比如:

1
2
Stream<Integer> lStr = list.stream().map(String::length);
lStr.map(s->s.toString()+" ").forEach(System.out::print);

结果输出:

4 10 6 3 1 3 3 2 6 5

4.6 flatMap

flatMap用于将多个流合并成一个流,俗称流的扁平化。这么说有点抽象,举个例子,比如现在需要将list中的各个元素拆分为一个个字母,并过滤掉重复的结果,你可能会这样做:

结果输出:

J a v a J a v a S c r i p t p y t h o n P H P C C + + C + + C # G o l a n g S w i f t

五、终端操作

下面所有操作都会用到如下基础数据:

1
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C", "C++",  "C#", "Golang", "Swift");

5.1 anyMatch

anyMatch方法用于判断流中是否有符合判断条件的元素,返回值为boolean类型。比如判断list中是否含有Java字符的元素:

1
System.out.println(list.stream().anyMatch(s -> s.contains("Java")));  //true

5.2 allMatch

allMatch方法用于判断流中是否所有元素都满足给定的判断条件,返回值为boolean类型。比如判断list中是否所有元素长度都不大于10:

1
System.out.println(list.stream().allMatch(s -> s.length() < 10));  //false

5.3 noneMatch

noneMatch方法用于判断流中是否所有元素都不满足给定的判断条件,返回值为boolean类型。比如判断list中不存在长度大于10的元素:

1
System.out.println(list.stream().noneMatch(s -> s.length() > 10));  //true

5.4 findAny

findAny方法用于返回流中的任意元素的Optional类型,例如筛选出list中任意一个以J开头的元素,如果存在,则输出它:

1
list.stream().filter(s->s.startsWith("J")).findAny().ifPresent(System.out::println);  //Java

5.5 findFirst

findFirst方法用于返回流中的第一个元素的Optional类型,例如筛选出list中长度大于5的元素,如果存在,则输出第一个:

1
list.stream().filter(s->s.length()>5).findFirst().ifPresent(System.out::println);  //JavaScript

5.6 reduce

reduce函数从字面上来看就是压缩,缩减的意思,它可以用于数字类型的流的求和,求最大值和最小值。

1
2
3
4
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
System.out.println(numbers.stream().reduce(0, Integer::sum)); // 16
numbers.stream().reduce(Integer::max).ifPresent(System.out::println); // 4
numbers.stream().reduce(Integer::min).ifPresent(System.out::println); // 1

5.7 count

count方法用于统计流中元素的个数,比如:

1
System.out.println(numbers.stream().count()); //7

5.8 forEach

forEach用于迭代流中的每个元素,最为常见的就是迭代输出,如:

1
numbers.stream().forEach(s -> System.out.print(s+" "));

结果输出

1 2 1 3 3 2 4

5.9 collect

collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map。举个例子:

1
2
3
4
5
List<String> filterList = list.stream().filter(s -> s.startsWith("C")).collect(Collectors.toList());
System.out.println(filterList.size()); //3

Set<String> filterSet = list.stream().filter(s -> s.startsWith("C")).collect(Collectors.toSet());
System.out.println(filterSet.size()); //3

六、并行流

6.1 并行流和顺序流差异

stream和parallelStream的简单区分:stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:

如果流中的数据量足够大,并行流可以加快处速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:

6.2 Optional

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。更详细说明请见:菜鸟教程Java 8 Optional类。

七、数据收集

我们了解到终端操作collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map。其实collect方法可以接受各种Collectors接口的静态方法作为参数来实现更为强大的规约操作,比如查找最大值最小值,汇总,分区和分组等等。

7.1 准备工作

为了演示Collectors接口中的静态方法使用,这里创建一个User类(人员类):

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String userId;
private String userName;
private boolean sex;
private String area;
private String cardId;
private Double performance;
private Date createTime;
private String status;
}

基础数据

1
2
3
4
5
6
7
8
9
List<User> luser = new LinkedList<>();
luser.add(new User(1, "4100001", "001号", true, "陕西西安","6682393002323223131", 16000.00, new Date(), "1"));
luser.add(new User(2, "4100002", "002号", true, "陕西西安","6682393002323223132", 13500.00, new Date(), "0"));
luser.add(new User(3, "4100003", "003号", true, "陕西咸阳","6682393002323223133", 12000.00, new Date(), "1"));
luser.add(new User(4, "4100004", "004号", true, "陕西宝鸡","6682393002323223134", 10000.00, new Date(), "1"));
luser.add(new User(5, "4100005", "005号", true, "陕西咸阳","6682393002323223135", 95000.00, new Date(), "0"));
luser.add(new User(6, "4100006", "006号", true, "陕西宝鸡","6682393002323223136", 18350.00, new Date(), "1"));
luser.add(new User(7, "4100007", "007号", true, "陕西渭南","6682393002323223137", 6000.00, new Date(), "1"));
luser.add(new User(8, "4100008", "008号", true, "陕西西安","6682393002323223138", 85000.00, new Date(), "0"));

7.2 遍历/匹配/筛选(foreach/find/match/filter)

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
        // 遍历输出符合条件的元素
luser.stream().forEach(System.out::println);

//匹配第一个
Optional<User> findFirst = luser.stream().filter(s -> s.getArea().equals("陕西宝鸡")).findFirst();
System.out.println("findFirst---" + findFirst);

//匹配任意(适用于并行流)
Optional<User> findAny = luser.parallelStream().filter(s -> s.getArea().equals("陕西咸阳")).findAny();
System.out.println("findAny---" + findAny);

boolean anyMatch = luser.stream().anyMatch(s -> s.getUserId().equals("4100009")); //false
System.out.println(anyMatch);

结果输出:

User(id=1, userId=4100001, userName=001号, sex=true, area=陕西西安, cardId=6682393002323223131, performance=16000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)
User(id=2, userId=4100002, userName=002号, sex=true, area=陕西西安, cardId=6682393002323223132, performance=13500.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=0)
User(id=3, userId=4100003, userName=003号, sex=true, area=陕西咸阳, cardId=6682393002323223133, performance=12000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)
User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)
User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=0)
User(id=6, userId=4100006, userName=006号, sex=true, area=陕西宝鸡, cardId=6682393002323223136, performance=18350.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)
User(id=7, userId=4100007, userName=007号, sex=true, area=陕西渭南, cardId=6682393002323223137, performance=6000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)
User(id=8, userId=4100008, userName=008号, sex=true, area=陕西西安, cardId=6682393002323223138, performance=85000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=0)
findFirst---Optional[User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=1)]
findAny---Optional[User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 13:48:44 CST 2021, status=0)]
false

7.3 聚合(max/min/count)

1
2
3
4
5
        Optional<User> max = luser.stream().max(Comparator.comparingDouble(User::getPerformance));
System.out.println("员工业绩最大值:" + max.get().getPerformance());

long count = luser.stream().filter(s -> s.getArea().equals("陕西西安")).count();
System.out.println("陕西西安的人数: " + count);

输出:

员工业绩最大值:95000.0
陕西西安的人数: 3

7.4 映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

将员工的业绩全部增加1000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        // 不改变原来员工集合的方式
List<User> userListNew = luser.stream().map(user -> {
User userNew = new User(user.getId(), user.getUserId(), user.getUserName(), user.isSex(), user.getArea(), user.getCardId(), null, user.getCreateTime(), user.getStatus());
userNew.setPerformance(user.getPerformance() + 1000);
return userNew;
}).collect(Collectors.toList());
System.out.println("一次改动前:" + luser.get(0).getUserId() + "-->" + luser.get(0).getPerformance());
System.out.println("一次改动后:" + userListNew.get(0).getUserId() + "-->" + userListNew.get(0).getPerformance());

// 改变原来员工集合的方式
List<User> userListNew2 = luser.stream().map(user -> {
user.setPerformance(user.getPerformance() + 1000);
return user;
}).collect(Collectors.toList());
System.out.println("二次改动前:" + luser.get(0).getUserId() + "-->" + luser.get(0).getPerformance());
System.out.println("二次改动前:" + userListNew2.get(0).getUserId() + "-->" + userListNew2.get(0).getPerformance());

输出;

一次改动前:4100001-->16000.0
一次改动后:4100001-->17000.0
二次改动前:4100001-->17000.0
二次改动前:4100001-->17000.0

将两个字符数组合并成一个新的字符数组

1
2
3
4
5
6
7
8
9
10
        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,7]
处理后的集合:[m, k, l, a, 1, 3, 5, 7]

7.5 归约(reduce)

求所有人员的业绩之和和最高业绩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 求工资之和方式1:
Optional<Double> sumSalary = luser.stream().map(User::getPerformance).reduce(Double::sum);
// 求工资之和方式2:
Integer sumSalary2 = luser.stream().reduce(0, (sum, p) -> sum += p.getPerformance().intValue(), (sum1, sum2) -> sum1 + sum2);
// 求工资之和方式3:
Integer sumSalary3 = luser.stream().reduce(0, (sum, p) -> sum += p.getPerformance().intValue(), Integer::sum);

// 求最高工资方式1:
Integer maxSalary = luser.stream().reduce(0, (max, p) -> max > p.getPerformance().intValue() ? max : p.getPerformance().intValue(),
Integer::max);
// 求最高工资方式2:
Integer maxSalary2 = luser.stream().reduce(0, (max, p) -> max > p.getPerformance().intValue() ? max : p.getPerformance().intValue(),
(max1, max2) -> max1 > max2 ? max1 : max2);

System.out.println("业绩之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
System.out.println("最高业绩:" + maxSalary + "," + maxSalary2);

输出:

业绩之和:255850.0,255850,255850
最高业绩:95000,95000

7.6 归集(toList/toSet/toMap)

collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect主要依赖java.util.stream.Collectors类内置的静态方法。

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。

下面用一个案例演示toList、toSet和toMap:

1
2
3
        Map<?, User> map = luser.stream().filter(p -> p.getPerformance() > 8000)
.collect(Collectors.toMap(User::getUserId, p -> p));
System.out.println("toMap:" + map);

输出:

toMap:{4100005=User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=0), 4100006=User(id=6, userId=4100006, userName=006号, sex=true, area=陕西宝鸡, cardId=6682393002323223136, performance=18350.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=1), 4100008=User(id=8, userId=4100008, userName=008号, sex=true, area=陕西西安, cardId=6682393002323223138, performance=85000.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=0), 4100001=User(id=1, userId=4100001, userName=001号, sex=true, area=陕西西安, cardId=6682393002323223131, performance=16000.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=1), 4100002=User(id=2, userId=4100002, userName=002号, sex=true, area=陕西西安, cardId=6682393002323223132, performance=13500.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=0), 4100003=User(id=3, userId=4100003, userName=003号, sex=true, area=陕西咸阳, cardId=6682393002323223133, performance=12000.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=1), 4100004=User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 14:39:16 CST 2021, status=1)}

7.7 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

计数:count
平均值:averagingInt、averagingLong、averagingDouble
最值:maxBy、minBy
求和:summingInt、summingLong、summingDouble
统计以上所有:summarizingInt、summarizingLong、summarizingDouble

案例:统计员工人数、平均收入、收入总额、最高收入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        // 求总数
Long count = luser.stream().collect(Collectors.counting());
// 求平均收入
Double average = luser.stream().collect(Collectors.averagingDouble(User::getPerformance));
// 求最高收入
Optional<Double> max = luser.stream().map(User::getPerformance).collect(Collectors.maxBy(Double::compare));
// 求收入之和
Double sum = luser.stream().collect(Collectors.summingDouble(User::getPerformance));
// 一次性统计所有信息
DoubleSummaryStatistics collect = luser.stream().collect(Collectors.summarizingDouble(User::getPerformance));

System.out.println("员工总数:" + count);
System.out.println("员工平均收入:" + average);
System.out.println("员工收入总和:" + sum);
System.out.println("员工收入所有统计:" + collect);

输出:

员工总数:8
员工平均收入:31981.25
员工收入总和:255850.0
员工收入所有统计:DoubleSummaryStatistics{count=8, sum=255850.000000, min=6000.000000, average=31981.250000, max=95000.000000}

7.8 分组(partitioningBy/groupingBy)

分区:将stream按条件分为两个Map,比如员工按收入是否高于8000分为两部分。

分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

将员工按收入是否高于15000分为两部分;将员工按性别和地区分组

1
2
3
4
5
6
7
8
9
       // 将员工按收入是否高于15000分组
Map<Boolean, List<User>> part = luser.stream().collect(Collectors.partitioningBy(x -> x.getPerformance() > 15000));
// 将员工按性别分组
Map<Boolean, List<User>> group = luser.stream().collect(Collectors.groupingBy(User::isSex));
// 将员工先按性别分组,再按地区分组
Map<Boolean, Map<String, List<User>>> group2 = luser.stream().collect(Collectors.groupingBy(User::isSex, Collectors.groupingBy(User::getArea)));
System.out.println("员工按收入是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);

输出:

员工按收入是否大于8000分组情况:{false=[User(id=2, userId=4100002, userName=002号, sex=true, area=陕西西安, cardId=6682393002323223132, performance=13500.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0), User(id=3, userId=4100003, userName=003号, sex=true, area=陕西咸阳, cardId=6682393002323223133, performance=12000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=7, userId=4100007, userName=007号, sex=true, area=陕西渭南, cardId=6682393002323223137, performance=6000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1)], true=[User(id=1, userId=4100001, userName=001号, sex=true, area=陕西西安, cardId=6682393002323223131, performance=16000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0), User(id=6, userId=4100006, userName=006号, sex=true, area=陕西宝鸡, cardId=6682393002323223136, performance=18350.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=8, userId=4100008, userName=008号, sex=true, area=陕西西安, cardId=6682393002323223138, performance=85000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0)]}
员工按性别分组情况:{true=[User(id=1, userId=4100001, userName=001号, sex=true, area=陕西西安, cardId=6682393002323223131, performance=16000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=2, userId=4100002, userName=002号, sex=true, area=陕西西安, cardId=6682393002323223132, performance=13500.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0), User(id=3, userId=4100003, userName=003号, sex=true, area=陕西咸阳, cardId=6682393002323223133, performance=12000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0), User(id=6, userId=4100006, userName=006号, sex=true, area=陕西宝鸡, cardId=6682393002323223136, performance=18350.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=7, userId=4100007, userName=007号, sex=true, area=陕西渭南, cardId=6682393002323223137, performance=6000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=8, userId=4100008, userName=008号, sex=true, area=陕西西安, cardId=6682393002323223138, performance=85000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0)]}
员工按性别、地区:{true={陕西咸阳=[User(id=3, userId=4100003, userName=003号, sex=true, area=陕西咸阳, cardId=6682393002323223133, performance=12000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=5, userId=4100005, userName=005号, sex=true, area=陕西咸阳, cardId=6682393002323223135, performance=95000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0)], 陕西宝鸡=[User(id=4, userId=4100004, userName=004号, sex=true, area=陕西宝鸡, cardId=6682393002323223134, performance=10000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=6, userId=4100006, userName=006号, sex=true, area=陕西宝鸡, cardId=6682393002323223136, performance=18350.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1)], 陕西渭南=[User(id=7, userId=4100007, userName=007号, sex=true, area=陕西渭南, cardId=6682393002323223137, performance=6000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1)], 陕西西安=[User(id=1, userId=4100001, userName=001号, sex=true, area=陕西西安, cardId=6682393002323223131, performance=16000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=1), User(id=2, userId=4100002, userName=002号, sex=true, area=陕西西安, cardId=6682393002323223132, performance=13500.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0), User(id=8, userId=4100008, userName=008号, sex=true, area=陕西西安, cardId=6682393002323223138, performance=85000.0, createTime=Mon Aug 30 14:52:01 CST 2021, status=0)]}}

7.9 接合(joining)

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

1
2
3
4
5
        String names = luser.stream().map(p -> p.getUserName()).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);

输出:

所有员工的姓名:001号,002号,003号,004号,005号,006号,007号,008号
拼接后的字符串:A-B-C

7.10 归约(reducing)

Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。

1
2
3
4
5
6
7
        // 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
Integer sum = luser.stream().collect(Collectors.reducing(0, p->p.getPerformance().intValue(), (i, j) -> (i + j - 5000)));
System.out.println("员工扣税薪资总和:" + sum);

// stream的reduce
Optional<Double> sum2 = luser.stream().map(User::getPerformance).reduce(Double::sum);
System.out.println("员工薪资总和:" + sum2.get());

输出:

员工扣税薪资总和:215850
员工薪资总和:255850.0

7.11 排序(sorted)

sorted,中间操作。有两种排序:
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):Comparator排序器自定义排序

案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        // 按收入升序排序(自然排序)
List<String> newList = luser.stream().sorted(Comparator.comparing(User::getPerformance)).map(User::getUserName)
.collect(Collectors.toList());
// 按收入倒序排序
List<String> newList2 = luser.stream().sorted(Comparator.comparing(User::getPerformance).reversed())
.map(User::getUserName).collect(Collectors.toList());
// 先按收入再按ID升序排序
List<String> newList3 = luser.stream()
.sorted(Comparator.comparing(User::getPerformance).thenComparing(User::getId)).map(User::getUserName)
.collect(Collectors.toList());
// 先按收入再按ID自定义排序(降序)
List<String> newList4 = luser.stream().sorted((p1, p2) -> {
if (p1.getPerformance().compareTo(p2.getPerformance())==0) {
return p2.getId() - p1.getId();
} else {
return (int)(p2.getPerformance() - p1.getPerformance());
}
}).map(User::getUserName).collect(Collectors.toList());

System.out.println("按收入升序排序:" + newList);
System.out.println("按收入降序排序:" + newList2);
System.out.println("先按收入再按ID升序排序:" + newList3);
System.out.println("先按收入再按ID自定义降序排序:" + newList4);

输出:

按收入升序排序:[007号, 004号, 003号, 002号, 001号, 006号, 008号, 005号]
按收入降序排序:[005号, 008号, 006号, 001号, 002号, 003号, 004号, 007号]
先按收入再按ID升序排序:[007号, 004号, 003号, 002号, 001号, 006号, 008号, 005号]
先按收入再按ID自定义降序排序:[005号, 008号, 006号, 001号, 002号, 003号, 004号, 007号]

7.12 提取/组合

流也可以进行合并、去重、限制、跳过等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        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]