Java Stream
Java8新添加功能,流Stream,以一种声明式的方式处理数据。Stream使用一种类似SQL语句从数据库查询的直观方式,来提供对Java集合运算和表达式的高阶抽象。
这种风格将要处理的元素集合看做一种流,流在管道中传输,可以在管道节点上进行处理,比如刷选,排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
以上的流程转换为 Java 代码为:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
什么是Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素 是特定类型的对象,形成一个队列。Stream并不会存储元素,而是按需计算
- 数据源,可以使集合、数组,I/Ochanel,产生器generator等。
- 聚合操作 类似SQL语句一样的操作,比如filter、map、reduce、find、match、stored等。
生成流
在 Java 8 中, 集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
forEach
Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取对应的平方数 List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
flatMap
扁平化流,将多维的集合转换为一维
List<List<Integer>> outer = new ArrayList<>();
List<Integer> inner1 = new ArrayList<>();
inner1.add(1);
List<Integer> inner2 = new ArrayList<>();
inner1.add(2);
List<Integer> inner3 = new ArrayList<>();
inner1.add(3);
outer.add(inner1);
outer.add(inner2);
outer.add(inner3);
//把流中集合装换为流
List<Integer> result = outer.stream().flatMap(inner -> inner.stream().map(i -> i + 1)).collect(Collectors.toList());
System.out.println(result);
peek
peek VS map
peek
操作一般用于不想改变流中元素本身的类型或者只想元素的内部状态时;而 map
则用于改变流中元素本身类型,即从元素中派生出另一种类型的操作。这是他们之间的最大区别。
那么 peek 实际中我们会用于哪些场景呢?比如对 Collection<T>
中的 T
的某些属性进行批处理的时候用 peek
操作就比较合适。如果我们要从 Collection<T>
中获取 T
的某个属性的集合时用 map
也就最好不过了。
peek的文档说明:peek主要被用在debug用途。
我们看下debug用途的使用:
Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
上面的例子输出:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
limit
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
sorted
sorted 方法用于对流进行排序。
一般情况下,Java的一些基础类(Integer、Long等)提供了Comparable接口实现,默认是升序(由小到大),不需要我们去实现比较方法。若要自定义比较方法,也有两种方式。
-
被排序的元素实现Comparable接口(实现compareTo()方法 推荐: stream流中默认比较)
-
实现比较器Comparator接口(实现compare()方法)
提示
小于、等于或者大于,分别返回负整数、0或者正整数。
stream流提供的sorted()排序方法
// 无参sorted,按照元素实现的Comparable接口方法进行比较(如果接口没有重写,默认升序。如果元素没有实现比较方法,调用后会抛异常java.lang.ClassCastException,类型转换为Comparable异常)
stream().sorted()
// 有参sorted,提供Comparator比较器
stream().sorted(new Comparator<Map.Entry<K, Node>>() {
@Override
public int compare(Map.Entry<K, Node> o1, Map.Entry<K, Node> o2) {
// 降序 大的放前面
return o2.getValue().compareTo(o1.getValue());
}
})
eg
Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);
findFirst findAny
findFirst和findAny,通过名字,就可以看到,对这个集合的流,做一系列的中间操作后,可以调用findFirst,返回集合的第一个对象,findAny返回这个集合中,取到的任何一个对象;
通过这样的描述,我们也可以知道,在串行的流中,findAny和findFirst返回的,都是第一个对象;
而在并行的流中,findAny返回的是最快处理完的那个线程的数据,
所以说,在并行操作中,对数据没有顺序上的要求,那么findAny的效率会比findFirst要快的;
https://blog.csdn.net/qq_28410283/article/details/80783505
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。
一般使用方式是在stream.collect()方法中调用Collectors.xx(),Collectors.xx()生成Collector对象
toList()
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
toMap()
Collectors.toMap()参数介绍
参数1 key
参数2 value
参数3 如果有重复的value,设置那个值为最终值
参数4 返回的Map类型
/**
* map过滤 value为空值的数据
*
* @param params
* @return
*/
public static Map<String, String> filterEmptyVal(Map<String, String> params) {
return params.entrySet()
.stream()
.filter(o -> !StringUtils.isEmpty(o.getValue()))
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldVal, newVal) -> oldVal,
LinkedHashMap::new
));
}
groupingBy()
Collectors.groupingBy()常用参数介绍
//参数1 分组的key(类似sql里面的group by后面的字段)
//参数2 映射取值
Collectors.groupingBy(p1, p2, p3)
基础
使用到1个参数,将产品类型分组,key类型id,value产品列表
Map<Integer, List<ProductDto>> resultList = couponList.stream().collect(Collectors.groupingBy(ProductDto::getProductType));
使用到2个参数,将产品类型分组,value是产品id列表
Map<Integer, List<Long>> resultList = products.stream().collect(Collectors.groupingBy(ProductDto::getProductType, Collectors.mapping(ProductDto::getId,Collectors.toList())));
也可以用另外的方式实现
Map<Integer, List<Long>> resultList = new HashMap<>();
products.forEach(a -> {
productIds.computeIfAbsent(a.getProductType(), key -> new ArrayList<>()).add(a.getId());
});
高级
使用到groupingBy和toMap
t_channel_lookup渠道商配置表,ltid配置类型、lkey渠道商id、lvalue存的值
sql根据ltid例如AES_TYPE秘钥、SOLD_TYPE售罄回调地址这两个配置类型查询所有渠道商信息looks
filter过滤掉空的value
通过lkey渠道商id分组,map的key是渠道商id
value是属性配置信息map,{配置类型:配置值},使用的toMap函数来修改分组后的取值
Map<String, Map<String, String>> lookMap = looks.stream()
.filter(l -> StringUtils.isNotBlank(l.getLvalue()))
.collect(
groupingBy(ChannelLookup::getLkey),
toMap(ChannelLookup::getLtid, ChannelLookup::getLvalue, (k1, k2) -> k1))
);
可以把这个数据结构想象成,key是渠道商id,value是渠道商配置对象
joining()
流中的每个元素加入分隔元素
String mergedString = strings.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
// 加入, 分隔符后,在字符串两边再加K(前缀 后缀)
Collectors.joining(", ", "K", "K")
统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
对于中间操作和终端操作的定义,请看《JAVA8 stream接口 中间操作和终端操作》,这篇主要讲述的是stream的count,anyMatch,allMatch,noneMatch操作,我们先看下函数的定义
long count();
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
count方法,跟List接口的size一样,返回的都是这个集合流的元素的长度,不同的是,流是集合的一个高级工厂,中间操作是工厂里的每一道工序,我们对这个流操作完成后,可以进行元素的数量的和;
剩下的三个方法,传入的都是Predicate的函数式接口,接口定义请看《JAVA8 Predicate接口》;
anyMatch表示,判断的条件里,任意一个元素成功,返回true
allMatch表示,判断条件里的元素,所有的都是,返回true
noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true