Java8 新特性之流式数据处理 #
一:流式处理简介 #
在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现。比如我们希望对一个包含整数的集合中筛选出所有的偶数,并将其封装成为一个新的List返回,那么在java8之前,我们需要通过如下代码实现:
List<Integer> numS = Lists.newArrayList();
for (int i = 0; i < 20; i++) {
numS.add(Zhou_StdRandom.uniform(1, 674));
}
List<Integer> evens = new ArrayList<>();
for (final Integer num : numS) {
if (num % 2 == 0) {
evens.add(num);
}
}
evens.stream().sorted().filter(integer -> true).forEach(integer -> System.out.println(integer));
通过java8的流式处理,我们可以将代码简化为:
List<Integer> evens = numS.stream().filter(integer -> integer % 2 == 0).collect(Collectors.toList());
先简单解释一下上面这行语句的含义,stream()操作将集合转换成一个流,filter()执行我们自定义的筛选处理,这里是通过lambda表达式筛选出所有偶数,最后我们通过collect()对结果进行封装处理,并通过Collectors.toList()指定其封装成为一个List集合返回。
由上面的例子可以看出,java8的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组、文件等,只要是可以转换成流,我们都可以借助流式处理,类似于我们写SQL语句一样对其进行操作。java8通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:
以集合为例,一个流式处理的操作我们首先需要调用stream()函数将其转换成流,然后再调用相应的中间操作达到我们需要对集合进行的操作,比如筛选、转换等,最后通过终端操作对前面的结果进行封装,返回我们需要的形式。
二:创建 #
- 流的创建有多种方式
- 从 Collection 和数组
- Collection.stream()
- Collection.parallelStream()
- Arrays.stream(T array) or Stream.of()
- 从 BufferedReader
- java.io.BufferedReader.lines()
- 静态工厂
- java.util.stream.IntStream.range()
- java.nio.file.Files.walk()
- 自己构建
- java.util.Spliterator
- 其它
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
三:中间操作 #
- 2.1 (过滤,顾名思义就是按照给定的要求对集合进行筛选满足条件的元素,java8提供的筛选操作包括:filter、distinct、limit、skip。)
filter
在前面的例子中我们已经演示了如何使用filter,其定义为:Stream
filter(Predicate<? super T> predicate),filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口,其包含一个test(T t)方法,该方法返回boolean。现在我们希望从集合students中筛选出所有武汉大学的学生,那么我们可以通过filter来实现,并将筛选操作作为参数传递给filter:
List<Student> whuStudents = students.parallelStream().sorted().filter(student -> "武汉大学".equals(student.getSchool())).collect(Collectors.toList());
whuStudents.parallelStream().forEach(student -> System.out.println(student));
distinct
distinct操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理,distinct基于Object.equals(Object)实现,回到最开始的例子,假设我们希望筛选出所有不重复的偶数,那么可以添加distinct操作:
List<Integer> numS = Lists.newArrayList();
for (int i = 0; i < 1000; i++) {
numS.add(Zhou_StdRandom.uniform(1, 24));
}
List<Integer> evens = numS.parallelStream().sorted().filter(integer -> integer%2==0).distinct().collect(Collectors.toList());
System.out.println(evens);
limit
limit操作也类似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,比如下面的例子返回前两个专业为土木工程专业的学生:
List<Student> civilStudents = students.parallelStream().sorted().filter(student -> "土木工程".equals(student.getMajor())).limit(2).collect(Collectors.toList());
civilStudents.parallelStream().forEach(student -> System.out.println(student));
skip
skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素,比如我们希望找出排序在2之后的土木工程专业的学生,那么可以实现为:
List<Student> civilStudents = students.parallelStream().sorted().filter(student -> "土木工程".equals(student.getMajor())).skip(2).collect(Collectors.toList());
civilStudents.parallelStream().forEach(student -> System.out.println(student));
- 2.2 (在SQL中,借助SELECT关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,在java8的流式处理中,主要包含两类映射操作:map和flatMap。)
map
举例说明,假设我们希望筛选出所有专业为计算机科学的学生姓名,那么我们可以在filter筛选的基础之上,通过map将学生实体映射成为学生姓名字符串,具体实现如下:
List<String> names = students.parallelStream().sorted().filter(student -> "计算机科学".equals(student.getMajor())).map(student -> student.getName()).collect(Collectors.toList());
System.out.println(names);
flatMap
flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流 。
String[] strs = {"java8", "is", "easy", "to", "use"};
List<String> distinctStrs = Arrays.stream(strs)
.map(s -> s.split("")).flatMap(strings -> Arrays.stream(strings))// 映射成为Stream<String[]>
.distinct().collect(Collectors.toList()); // 扁平化为Stream<String>
System.out.println(distinctStrs);
四:终端操作 #
allMatch
allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的学生都已满18周岁,那么可以实现为
boolean isAdult = students.parallelStream().allMatch(student -> student.getAge() >= 18);
System.out.println(isAdult?"是":"否");
anyMatch
anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true,例如我们希望检测是否有来自武汉大学的学生,那么可以实现为:
boolean hasWhu = students.parallelStream().anyMatch(student -> "武汉大学".equals(student.getSchool()));
System.out.println(hasWhu?"是":"否");
noneMathch
noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true,例如我们希望检测是否不存在专业为计算机科学的学生,可以实现如下:
boolean noneCs = students.parallelStream().noneMatch(student -> "计算机科学".equals(student.getMajor()));
System.out.println(noneCs?"是":"否");
findFirst
findFirst用于返回满足条件的第一个元素,比如我们希望选出专业为土木工程的排在第一个学生,那么可以实现如下:
Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();
System.out.println(optStu.get());
五:常见的操作可以归类如下 #
Intermediate(中间)
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
Terminal(终端)
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
Short-circuiting(截取)
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
六转换 #
- list 转 Map <key,list>
package com.tool.stream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Demo1 {
public static class TestEntity {
private Integer id;
private String name;
public TestEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
public TestEntity() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "TestEntity{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
List<TestEntity> entityList = new ArrayList<>(2) ;
entityList.add(new TestEntity(1,"图书馆"));
entityList.add(new TestEntity(2,"艺术馆"));
entityList.add(new TestEntity(1,"游泳池"));
// Map<Integer, TestEntity> entityMap = entityList.stream().collect(Collectors.toMap(TestEntity::getId, user -> user));
//下面的可以确保 百分之百 在key重复下不报错
Map<Integer, TestEntity> entityMap = entityList.stream().collect(Collectors.toMap(TestEntity::getId, Function.identity(), (oldValue, newValue) -> newValue));
// Map<Integer, String> stringMap = entityList.stream().collect(Collectors.toMap(TestEntity::getId, user -> user.getName()));
Map<Integer, String> stringMap = entityList.stream().collect(Collectors.toMap(TestEntity::getId, t -> t.getName(), (oldValue, newValue) -> newValue));
Map<Integer, List<TestEntity>> integerListMap = entityList.stream().collect(Collectors.groupingBy(oo -> oo.getId()));
System.out.println(entityMap.get(1));
System.out.println(stringMap.get(1));
System.out.println(integerListMap.get(1));
/*
TestEntity{id=1, name='游泳池'}
游泳池
[TestEntity{id=1, name='图书馆'}, TestEntity{id=1, name='游泳池'}]
* */
}
}
- list 分组统计
package com.tool.stream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Demo2 {
public static class TestEntity {
private String uuid;
private Double price;
private String name;
public String toString() {
return "TestEntity{" +
"uuid='" + uuid + '\'' +
", price=" + price +
", name='" + name + '\'' +
'}';
}
public TestEntity(String uuid, Double price, String name) {
this.uuid = uuid;
this.price = price;
this.name = name;
}
public TestEntity() {
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
Function<Double, Double> function = (oo -> {
Random random = new Random(System.currentTimeMillis());
return random.nextDouble() * 100 * oo;
});
List<TestEntity> entityList = new ArrayList<>();
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(100.01d), "a"));
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(199.01d), "a"));
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(97.01d), "b"));
Map<String, List<TestEntity>> listMap = entityList.stream().collect(Collectors.groupingBy(obj -> obj.getName()));
Map<String, Long> longMap = entityList.stream().collect(Collectors.groupingBy(obj -> obj.getName(), Collectors.counting()));
Map<String, DoubleSummaryStatistics> doubleSummaryStatisticsMap = entityList.stream().collect(Collectors.groupingBy(obj -> obj.getName(), Collectors.summarizingDouble(TestEntity::getPrice)));
doubleSummaryStatisticsMap.entrySet().stream().forEach(obj -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("最大值:" + obj.getValue().getMax());
stringBuilder.append("最小值:" + obj.getValue().getMin());
stringBuilder.append("合计值:" + obj.getValue().getSum());
stringBuilder.append("平均值:" + obj.getValue().getAverage());
stringBuilder.append("数量:" + obj.getValue().getCount());
System.out.println(stringBuilder.toString());
});
/*
最大值:14170.806808512518最小值:7121.362689911748合计值:21292.169498424264平均值:10646.084749212132数量:2
最大值:6907.74317116627最小值:6907.74317116627合计值:6907.74317116627平均值:6907.74317116627数量:1
* */
}
}
- Map&Reduce 收集器 (非常重要)
map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个
package com.tool.stream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Demo3 {
public static class TestEntity {
private String uuid;
private Double price;
private String name;
public String toString() {
return "TestEntity{" +
"uuid='" + uuid + '\'' +
", price=" + price +
", name='" + name + '\'' +
'}';
}
public TestEntity(String uuid, Double price, String name) {
this.uuid = uuid;
this.price = price;
this.name = name;
}
public TestEntity() {
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
List<TestEntity> entityList = new ArrayList<>();
Function<Double, Double> function = (oo -> {
Random random = new Random(System.currentTimeMillis());
return random.nextDouble() * 100 * oo;
});
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(100.01d), "a"));
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(199.01d), "a"));
entityList.add(new TestEntity(UUID.randomUUID().toString(), function.apply(97.01d), "b"));
entityList.stream().collect(Collectors.groupingBy( obj -> obj.getName() ,Collectors.summarizingDouble(ooEntity -> ooEntity.getPrice()))).forEach((s, obj) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("名称:").append(s).append(" ") ;
stringBuilder.append("最大值:" + obj.getMax());
stringBuilder.append("最小值:" + obj.getMin());
stringBuilder.append("合计值:" + obj.getSum());
stringBuilder.append("平均值:" + obj.getAverage());
stringBuilder.append("数量:" + obj.getCount());
System.out.println(stringBuilder.toString());
});
System.out.println(Arrays.toString(entityList.stream().map(obj -> obj.toString()).collect(Collectors.toList()).toArray())); ;
Double sumValue = entityList.stream().map(obj -> obj.getPrice()).reduce((sum, res) -> sum + res).get();
System.out.println("sumValue:"+sumValue);
}
}
- Collector 收集器 (非常重要)
七:自己总结的一些好用方法 #
- 去重
/**
* 去重
* 一个对象的去重例子 return list.stream().filter(StreamUtils.distinctByKey(o -> o.getId())).collect(Collectors.toList()); 如果不是id,是其它字段 同理 o.getId() 换为 o.getOther()
*
* @param keyExtractor
* @param <T>
* @return
*/
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}