JDK8 集合 Stream 入门

JDK 8 stream 是什么

本文所说的stream 跟java IO 里的stream有点区别, 虽然都是流的意思, 但本文所写的stream 是指 JDK8 API的新成员(还有lambda 和 try-with-resource) , 它允许以生命的方式处理数据集合。



特点

1. 代码简洁

函数式编程写出的代码简洁且而且可读性不差, 摆脱for循环

2. 多核友好 (parallelStream())

java函数式编程使得并行程序开发更加简单, 也就是将相对于for 循环性能提高了。



流程

1. 第一步, 把集合转换为流stream
2. 第二步, 操作stream流

stream流在管道中经过中间操作(intermediate operation)的处理, 最后由最终操作(terminal operation)得到前面管道处理的结果。


操作符

两种: 中间操作符 和 终止操作符

中间操作符
流方法DescriptionExample
filter设置条件过滤集合中的元素List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”);
List filteredStrList=strList.stream().filter(x->x.contains(“c”)).collect(Collectors.toList());
filteredStrList.forEach(x->log.info(x));
distinct根据条件去重, 如果是集合是一些对象, 则相对应的类要重写hashcode()和equal(), 不支持对对象某个属性去重,如果要实现根据某个属性去重则必须用filter(distinctByKey(x->x.getId())List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
List distinctedStrList = strList.stream().distinct().collect(Collectors.toList());
limit会返回一个不超过指定长度的流,用于分页, 相当于sql的top()List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
List limitedStrList = strList.stream().limit(3).collect(Collectors.toList());
skipReturns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned. 用于除掉前n个元素List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”);
List filteredStrList = strList.stream().filter(x->x.contains(“c”)).collect(Collectors.toList());
map对集合里每个元素里进行处理List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
List mapStrList = strList.stream().map(x->x + “!!”).collect(Collectors.toList());
flatMap流的扁平化处理, 集合里的集合元素转换成1个合并后的流, 和map的返回值对比请参考下面的例子
sorted根据规则排序, 注意这里是stream的sort, 注意和List.sort()的区别 https://editor.csdn.net/md/?articleId=126576104List sortedUserList = userList.stream().sorted(Comparator.comparing(MyUser::getName).reversed()
.thenComparing(MyUser::getId)).collect(Collectors.toList());



根据对象元素某个属性去重.

上面提到了, distinct()只会根据对象本身的Hashcode 和equal 去重. 如果想根据对象某个属性去重.
实际场景:
在Hibernate Envers(record level audit 框架)的 AuditReader中会用到。去重以获得pk列表。
这时我们需要定义1个distinctbyKey的方法:

private 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;
    }

而利用Fitler 和 distinctBykey 方法来去重, 下面是例子

 public void example(){
        List<MyUser> userList = new ArrayList<>();
        userList.add(new MyUser(3L,"Jack"));
        userList.add(new MyUser(3L,"Bill"));
        userList.add(new MyUser(4L,"Alice"));

        List<MyUser> disUserList = userList.stream().distinct().collect(Collectors.toList());
        log.info(String.valueOf(disUserList));
        List<MyUser> disUserList2 = userList.stream().filter(distinctByKey(x->x.getId())).collect(Collectors.toList());
        log.info(String.valueOf(disUserList2));

    }



flatMap 和 map 的区别

我们首先编写1个工具方法, 把字符串转成字符List的流

class Tools{
     public static Stream<Character> getCharListStream(String str){
        List<Character> characterList = new ArrayList<>();
        for (char x : str.toCharArray()){
            characterList.add(x);
        }
        return characterList.stream();
    }
}

我们先看用map的情况

 /**
   * map(Tools::getCharListStream) return a Stream<Stream<Character>> Object
 */
@Slf4j
class StreamMap2 {
    public void example1(){
         List<String> strList = Arrays.asList("abc","cde","BBB","ccc","ecdd","bbbbb1","cde","BBB","ccc");
        Stream<Stream<Character>> mapStrStream = strList.stream().map(Tools::getCharListStream);
        List<Stream<Character>> mapStrStreamList = mapStrStream.collect(Collectors.toList());
        List<List<Character>> mapStrListList = strList.stream().map(Tools::getCharListStream).collect(Collectors.toList())
                .stream().map(x->x.collect(Collectors.toList())).collect(Collectors.toList());
        log.info(String.valueOf(mapStrStream));
        log.info(String.valueOf(mapStrStreamList));
        log.info(String.valueOf(mapStrListList));
    }
}

可以当我们用map和使用上面的工具类去转换String 到 字符串数组时, 返回的是1个Stream<Stream<Character>> 对象。
也就是字符串数组流的数组 的流

但是我们可以将里面的Stream<Charactor>对象利用Collect()函数转换成List<Character>。然后输出

输出

20:47:45.826 [main] INFO com.home.javacommon.study.collectionstream.StreamMap2 - java.util.stream.ReferencePipeline$3@1ee0005
20:47:45.827 [main] INFO com.home.javacommon.study.collectionstream.StreamMap2 - [java.util.stream.ReferencePipeline$Head@75a1cd57, java.util.stream.ReferencePipeline$Head@3d012ddd, java.util.stream.ReferencePipeline$Head@6f2b958e, java.util.stream.ReferencePipeline$Head@1eb44e46, java.util.stream.ReferencePipeline$Head@6504e3b2, java.util.stream.ReferencePipeline$Head@515f550a, java.util.stream.ReferencePipeline$Head@626b2d4a, java.util.stream.ReferencePipeline$Head@5e91993f, java.util.stream.ReferencePipeline$Head@1c4af82c]
20:47:45.827 [main] INFO com.home.javacommon.study.collectionstream.StreamMap2 - [[a, b, c], [c, d, e], [B, B, B], [c, c, c], [e, c, d, d], [b, b, b, b, b, 1], [c, d, e], [B, B, B], [c, c, c]]

十分合理
第1个对象 flatMapStrStream 输出只是它的hashcode
第1个数组mapStrStreamList 里面的元素是Stream<Charactor> 对象, 输出的是Hascode 列表
第2个数组里面的mapStrListList 里面的元素是List<Character> 对象, 输出的是直观的字符串数组了。

我们再看看FlatMap
首先, FlatMap里面的参数必须是1个流对象的函数型接口对象, 而不是1个简单的函数型接口对象(Map的参数可以是。

也就是将, 如果不可以直接当Map来使用。
下面的代码是错误的
在这里插入图片描述

由于我们上面工具类方法返回的类型是Stream<Charater>, 则是适用的。
例子:

@Slf4j
/**
    * flatMap(Tools::getCharListStream) return a Stream<Character> Object (merged multiple Stream<Characters> to a single one
 */
class StreamFlatMap1 {
    public void example1(){
        List<String> strList = Arrays.asList("abc","cde","BBB","ccc","ecdd","bbbbb1","cde","BBB","ccc");
        //List<Character> flatMapStrSreamList= strList.stream().flatMap(x->x.concat("sdf")).collect(Collectors.toList());
        Stream<Character> flatMapStrStream= strList.stream().flatMap(Tools::getCharListStream);
        List<Character> flatMapStrList = flatMapStrStream.collect(Collectors.toList());
        log.info(String.valueOf(flatMapStrStream));
        log.info(String.valueOf(flatMapStrList));
    }
}

可以见到 flatMap返回的是1个Stream<Charactor> 的对象, 而不是map函数的List<Stream<Charactor>>
再看输出:

20:47:45.828 [main] INFO com.home.javacommon.study.collectionstream.StreamFlatMap1 - java.util.stream.ReferencePipeline$7@cac736f
20:47:45.829 [main] INFO com.home.javacommon.study.collectionstream.StreamFlatMap1 - [a, b, c, c, d, e, B, B, B, c, c, c, e, c, d, d, b, b, b, b, b, 1, c, d, e, B, B, B, c, c, c]

也就是将, flatMap把里面的内容合并了在返回1个合并后数组的流…
map返回一个值, flatMap必须返回一个流, 多个值
这就是区别, 也就是把原来的数组降维了, 就是所谓的扁平化…

终止操作符
流方法DescriptionExample
anyMarch返回boolean, 检查Stream是否存在至少1个符合条件的元素List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
boolean isThere = strList.stream().map(x->x + “!!”).anyMatch(x->x.length() < 5);
log.info(String.valueOf(isThere));
allMarch返回boolean, 检查Stream是否每个元素都满足提供的条件List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
boolean isThere = strList.stream().map(x->x + “!!”).allMatch(x->x.length() > 5);
log.info(String.valueOf(isThere));
noneMarch返回boolean, 检查Stream是否每个元素都不满足条件List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
boolean isThere = strList.stream().map(x->x + “!!”).anyMatch(x->x.length() < 5);
log.info(String.valueOf(isThere));
findFrist返回stream里的第1个元素List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
Optional first = strList.stream().map(x->x + “!!”).filter(x->x.length() > 5).findFirst();
log.info(String.valueOf(first.get()));
findAny返回stream里的第任意1个元素,在串型stream等于findFirst, 一般用于parallelStreamList strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
Optional any = strList.parrallelstream().map(x->x + “!!”).filter(x->x.length() > 5).findAny();
log.info(String.valueOf(any.get()));
forEach遍历每一个元素,执行操作, 优点类似于中间操作符的Map,但是相对于Map,ForEach是有序的, 对比于外部的for循环性能会更高。跟List的forEach很类似List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
strList.stream().map(x->x + “!!”).filter(x->x.length() > 5).forEach(x->log.info(x));
collect将流转换返回集合,而且可以转换为其他形式的流(例如list->set去重)厉害了List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
Set strSet = strList.stream().collect(Collectors.toSet());;
log.info(String.valueOf(strSet));
reduce别被单词字面意思误导, reduce用于对集合中所有元素进行1个总的操作(例如所有元素相加/平均…), 右面reduce方法第一个参数identity是计算结果都初始值,接口方法参数的acc代表计算结果, item表示元素List strList = Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”);
String sumStr = strList.stream().reduce(“sum:”, (acc, item)-> acc.concat(item));
count返回元素结果, 其实用集合的size()一样的…Arrays.asList(“abc”,“cde”,“BBB”,“ccc”,“ecdd”,“bbbbb1”,“cde”,“BBB”,“ccc”).stream().count()
max获得最大的元素, 必须提供Comparator参数MyUser maxUser = userList.stream().max(Comparator.comparing(MyUser::getId).thenComparing(MyUser::getName)).get();
collect toMap 的方法

其实这个方法一般用于把1个对象数组 创建1个对象的MAP, 其中\指定对象某个属性作为key。
例子:

public void example1(){
        List<MyUser> userList = new ArrayList<>();
        userList.add(new MyUser(4L,"Alice"));
        userList.add(new MyUser(3L,"Jack"));
        userList.add(new MyUser(3L,"Bill"));
        userList.add(new MyUser(2L,"Ted"));

        Map<Long,MyUser> myUserMap = userList.stream().collect(Collectors.toMap(MyUser::getId, v->v, (a,b)->a));
        log.info(String.valueOf(myUserMap));
    }

toMap()中第1个参数 MyUser::getId, 就是指定对象中哪个属性作为key
v->v 表示以对象本身作为值, 当然这里很多操作空间(见下1个例子)。 而且可以用Function.identity() 来代替v->v

源码:

    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }

(a,b)->a, 表示如果某两个对象的key属性重复, 则取前1个or后1个对象作为值。
如果没有这个参数, 如果数字出现两个具有重复key的对象, 则会抛出下面的异常。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key MyUser(id=3, name=Jack)

输出:

{2=MyUser(id=2, name=Ted), 3=MyUser(id=3, name=Jack), 4=MyUser(id=4, name=Alice)}



进阶了, 如果对象中没有属性适合做key,如何在toMap()方法里,用自增的数字生成key?

参考:https://stackoverflow.com/questions/51047522/how-to-auto-increment-the-key-of-a-hashmap-using-collectors-and-stream-in-java-8
需求: 下面构建了4个User对象的数组userList, 其中 getId()的属性有重复, 并不适合做key。
如何利用stream的toMap方法来构造个新的Map, 以1,2,3,4… 自增数字作为key。

        List<MyUser> userList = new ArrayList<>();
        userList.add(new MyUser(4L,"Alice"));
        userList.add(new MyUser(3L,"Jack"));
        userList.add(new MyUser(3L,"Bill"));
        userList.add(new MyUser(2L,"Ted"));

Step 1. 构造1个 指定size 的LongStream 对象.

    IntStream intStream = IntStream.range(0, userList.size());

上面的代码是构造1个IntStream, 从0开始, 数量是userList的元素个数(4),
range()方法,创建一个以1为增量步长,从startInclusive(包括)到endExclusive(不包括)的有序的数字流。

至于不是用LongStream, 毕竟List的元素上线就是Integer 上线, list 用get(Integer i) 来获得元素。

Step 2. 利用boxed(装箱)函数获得1个stream对象。

 Stream iStream = intStream.boxed();

boxes()的源代码:

    @Override
    public final Stream<Integer> boxed() {
        return mapToObj(Integer::valueOf);
    }

这样我们的得到1个基于Integer 0,1,2,3 的流, 然后基于这个流去而不是UserList去创建1个Map。

Map<Integer, MyUser> userMap = iStream.collect(Collectors.toMap(k->k, k->userList.get(k)));

这样我们就实现了需求了。
也明白我们的IntStream 为何要从0开始, 因为userList第一元素就是userList.get(0), 如何用1就必须写成userList.get(k-1)了。

当然我们也可以整合一下代码:

  public void example2(){
        List<MyUser> userList = new ArrayList<>();
        userList.add(new MyUser(4L,"Alice"));
        userList.add(new MyUser(3L,"Jack"));
        userList.add(new MyUser(3L,"Bill"));
        userList.add(new MyUser(2L,"Ted"));
        
        Map<Integer, MyUser> userMap = IntStream.range(0, userList.size()).boxed().collect(Collectors.toMap(Function.identity(), userList::get));
        log.info(String.valueOf(userMap));
    }

代码越来越高大上了。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值