Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
文章目录
1:函数式编程
在很多其他的编程语言里面,都可以实现函数式的编程,也就是函数可以作为变量去灵活使用,但是java一直都不可以,之前很多都使用一些匿名内部类这种丑的亚批的代码。java8之后算是可以使用伪函数式编程,其实也是应用了单方法接口去实现。并且设计出了lambda语法。
1.1:lambda
在lambda之前实现thread(真的很丑这代码)。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xxx");
}
});
在lambda实现thread
Thread thread = new Thread(()-> System.out.println("xx"));
1.2:函数式接口
其实上面的runnable接口就是一个函数式接口,标志就是用@FunctionalInterface实现的接口,我们也可以自己实现一个:
@FunctionalInterface
public interface FunctionInterfaceDemo {
String run(int i);
//如果写两个方法就会报错
}
然后就可以当成函数变量使用了,比如说你根据不同的入参调整成不同执行策略fuction,最后去执行这个fuction,就非常的函数式了。
FunctionInterfaceDemo functionDemo = p->{
p++;
return String.valueOf(p);
};
2:大杀器-stream
如果说上面lambda只是美化了代码而已,那么lambda配合stream使用就是非常装逼的无敌大杀器了。
2.1:foreach
其实最常用的就是foreach了,直接展示一个demo,可以用lambda形式进行遍历
//构建个User对象的list,后面都会使用到 User(id,name,age)
List<User> userList = Lists.newArrayList();
userList.add(new User(1, "aaa", 18));
userList.add(new User(2, "bbb", 21));
userList.add(new User(3, "ccc", 25));
userList.stream().forEach(user->{
System.out.println(user.getName());
});
2.2:filter
filter可以按照你的要求进行过滤,非常骚包
userList.stream().filter(user -> user.getAge() > 18).forEach(user -> {
//输出age大于18的user的name
System.out.println(user.getName());
});
2.3:collect实现list转map
collect可以实现各种集合之间按照一定自定义规则的转换,最为典型的就是list转换map的例子
Map<String, User> map = userList.stream().collect(Collectors.toMap(User::getName, pojo -> pojo, (k1, k2) -> k1));
//【解释下toMap的3个参数】:
//第一个参数是用哪个字段作为key
//第二个参数是map的value类型,可以取原来的对象,也可以是原来对象的某个字段 比如pojo->pojo改为 User::getAge 就是value只收集user的age字段
//第三个参数(k1, k2) -> k1)是可选的,在key冲突的时候丢弃其实一个,如果没有这个参数会直接报错
//这边建议第三个参数必须强制设置一个,不然要是重复key程序直接会报错,非常麻瓜。
20220909补充:原文链接
- 转成一对一的,一个id对应一个对象,后面的k1,k2 是指定一种覆盖规则,防止key冲突
Map<Long, User> maps = userList.stream()
.collect(Collectors.toMap(User::getId, Function.identity(), (k1, k2) -> k2));
- 转成一对一的,一个id对应一个属性
Map<Long, String> maps = userList.stream()
.collect(Collectors.toMap(User::getId, User::getAge, (k1, k2) -> k2));
- 转成一对多的,一个id对应多个对象
Map<Integer, List<Apple>> groupBy = appleList.stream()
.collect(Collectors.groupingBy(Apple::getId));
2.4:collect+map抽取pojo对象的某个字段
List<String> names = userList.stream().map(User::getName)
.collect(Collectors.toList());
2.5:list实现排序
//数字类型list排序
List<Integer> sortList = list.stream().sorted(Comparator.comparing(p -> p))
.collect(Collectors.toList());
//对象类型list排序
List<Pair> sortList = list.stream()
.sorted((p1, p2) -> p2.getValue() - p1.getValue()).collect(Collectors.toList());
// 这里是逆序 ,如果是跑p1-p2就是自然序
2.6:Predicate和Consumer
在这些stream的各种操作组合骚包操作下,有时候直接写一个lambda显得有点丑,特别是这个lambda方法体内东西很多的时候,这个时候就可以使用这2个内置的函数先行定义lambda方法体,这样就非常灵活骚包,而且还可以分开写,痛击你的同事让他看不懂。
Predicate<User> predicate = p -> p.getName().startsWith("s");
Consumer<User> consumer = p -> {
System.out.println(p.getName());
System.out.println(p.getAge());
};
userList.stream().filter(predicate).forEach(consumer);
2.7:Stream和parallelStream的区别
更详细的讲解:Stream和parallelStream的区别
stream是单管道流,parallelStream是多管道流。建议符合以下条件时使用parallerStream:
- 不关心执行顺序
- 没有并行处理的并发问题
- 处理事件涉及io阻塞操作,业务处理事件较长
下面我们来测试下stream和parallerStream的执行对比:
List<User> userList = Lists.newArrayList();
for (int i = 0; i < 1000; i++) {
userList.add(new User(1, "aaa", 18));
}
AtomicInteger count = new AtomicInteger(0);
long start = System.currentTimeMillis();
userList.parallelStream().forEach(p -> {
count.addAndGet(p.getId());
//模拟业务处理
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long duration = System.currentTimeMillis() - start;
System.out.println("count = "+count.get()+" duration = " + duration);
//执行结果如果换成stream就会慢很多
2.8:实现原理和forkjoin框架
其实stream和parallelStream都是通过fork/join框架去执行的,大概是做了这么几件事
- 使用自带的ForkJoinPool.commonPool()这个公共线程池去执行任务
- 然后这些任务会被拆分成几个小任务进行对应的流式处理
- 处理完成后类似future这种方式得到所有结果集再进行合并
这里有一点特别要注意,stream和parallelStream的第一个任务都是默认使用主线程的,所以异常不会逃逸,但是parallelStream的任务数量一旦大于1,就会启动forkjoin的线程,如果不处理好异常,异常就会逃逸掉了。
3:总结
总的来说lambda加stream的组合够骚够装逼,也能简化我们开发的复杂度,并且让代码更加帅!推荐使用!
其他还有很多stream的各种方法可以按照实际场景去尝试使用看看哦!