Java Stream

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接口实现,默认是升序(由小到大),不需要我们去实现比较方法。若要自定义比较方法,也有两种方式。

  1. 被排序的元素实现Comparable接口(实现compareTo()方法 推荐: stream流中默认比较

  2. 实现比较器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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值