本文首发于 https://icdream.github.io/2019/02/24/java0801/ 的个人博客, 点击获得更好的阅读体验!
前段时间公司书架多了一本《Java8 实战》, 毕竟久闻 lambda 的大名, 于是借来一阅. 这一看, 简直是惊为天人啊, lambda,stream,java8 里简直是满脑子骚操作, 看我的一愣一愣的. 我甚至是第一次感觉到了什么叫优雅.
本文主要介绍 java8 中的流处理, 看看 java8 是怎么愉快的玩耍集合的, 让我们来一起感受 java8 的魅力吧!
我就随便举个例子, 看看 Stream 有多优雅.
- // 对苹果按颜色汇总并绩数量
- Map<String, Long> appleCount = apples.stream()
- .collect(groupingBy(Apple::getColor, counting()));
- // 过滤掉颜色为黑色的苹果, 并汇总好苹果的总金额
- Double sum = apples.stream()
- .filter(i->"black".equals(i.getColor()))
- .collect(toList);
一, lambda 表达式
虽然本文重点是 stream, 但是 stream 中需要传递 lambda 表达式, 所以简单介绍一下 lambda 表达式. lambda 表达式其实就是匿名函数 (anonymous function), 是指一类无需定义标识符的函数或子程序.
java 中匿名函数的表现形式, 只留下入参和方法体中的内容
- // 普通函数
- public void run(String s){
- System.out.print(s+"哈哈");
- }
- // 我不要名字啦!!!
- (s)->System.out.print(s+"哈哈")
诶, 过去我们都用对象调方法的, 你弄这个没名的东西啥时候用啊?
java 中我们通过函数式接口来使用这种匿名函数.
函数式接口
1.java 中只包含一个未实现方法的接口. 其中可以有与 Object 中同名的方法和默认方法 (java8 中接口方法可以有默认实现).
2.java 中函数式接口使用 @FunctionalInterface 进行注解. Runnable,Comparator 都是函数式接口.
3.java.util.function 包下为我们提供很多常用的函数式接口, 例如 Function 等.
用法举例:
- // 实现 Runnable 中的 run 方法, 替代匿名内部类.
- Runnable r = ()->System.out.print("哈哈");
- // 作为参数传递.
- new Thread(()-> System.out.println("haha")).start();
- ArrayList<Apple> list = new ArrayList<>();
- list.forEach(i-> System.out.println(i.getWeight()));
- // 简化策略模式
- public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
- List<Apple> apples = new ArrayList<>();
- for(Apple apple : inventory){
- if(p.test(apple)){
- apples.add(apple);
- }
- }
- return apples;
- }
- public class BigApple implement ApplePredicate{
- @Override
- public boolean test(Apple a){
- if(a.getWeight>10){
- return a
- }
- }
- }
- // 这是个简单的策略模式, 根据用户的需要, 创建不同的接口 ApplePredicate 实现类, 调用时传入不同的实现类就可以, 但问题是如果需求过多, 创建的实现类也会很多, 过于臃肿不方便管理.
- xx.filterApple(inventory,new BigApple);
- // 使用 lambda 表达式, 不在需要创建 BigApple 类
- xx.filterApple(inventory,i->(i.getWeight>10));
使用 lambda 表达式可以简化大量的模板代码, 并且可以向方法直接传递代码.
总之
方法出参入参来自函数式接口
- // 入参 s, 返回 void
- (s)->System.out.println(s);
- // 入参空, 返回 void
- ()->System.out.print("haha");
- // 入参 i, 返回 i+1
- i->i+1
- // 后面写代码块
- apple->{if(apple.getWeiht>5) return "BIG";
- else return "small";
- }
好了, 不多啰嗦了, 如果感兴趣推荐下面的文章或《Java8 实战》的前三章.
1.Lambda 表达式有何用处? 如何使用?
2.java8 实战 http://product.dangdang.com/23952002.html
二, Stream
流是什么?
Java API 的新成员, 它允许你使用声明式方式处理数据集合 (类似 sql, 通过查询语句表达, 而不是临时编写一个实现).
如果有人说 lambda 表达式不易于理解, 那还勉强可以接受 (其实过于复杂的 lambda 缺失不好阅读, 但通常 lambda 不会做太复杂的实现), 但流真的非常的易懂易用. 这个语法糖真的是甜死了.
注意事项:
1. 流只能使用一次, 遍历结束就代表这个流被消耗掉了
2. 流对集合的操作属于内部迭代, 是流帮助我们操作, 而不是外部迭代
3. 流操作包含: 数据源, 中间操作链, 终端操作三个部分.
基础流操作
- List<Double> collect = list.stream()
- // 过滤掉黑色的苹果
- .filter(i -> "black".equals(i.getColor()))
- // 让苹果按照重量个价格排序
- .sorted(Comparator.comparing(Apple::getWeight)
- .thenComparing(i->i.getPrice()))
- // 筛选掉重复的数据
- .distinct()
- // 只要苹果的价格
- .map(Apple::getPrice)
- // 只留下前两条数据
- .limit(2)
- // 以集合的形式返回
- .collect(toList());
- // 循环打印列表中元素
- list.forEach(i->System.out.print(i));
Apple::getPrince<=>i -> i.getPrince() 可以看做是仅涉及单一方法的语法糖, 效果与 lambda 表达式相同, 但可读性更好.
同理
下面列表为常见操作
中间
操作 | 类型 | 作用 | 函数描述 | 函数 |
---|---|---|---|---|
filter | 中间 | 过滤 | T -> boolean | Predicate |
sorted | 中间 | 排序 | (T,T)->int | Comparator |
map | 中间 | 映射 | T->R | Function<T,R> |
limit | 中间 | 截断 | ||
distinct | 中间 | 去重,根据 equals 方法 | ||
skip | 中间 | 跳过前 n 个元素 |
终端
操作 | 类型 | 作用 |
---|---|---|
forEach | 终端 | 消费流中的每个元素,使用 lambda 进行操作 |
count | 终端 | 返回元素个数,long |
collect | 终端 | 将流归约成一个集合,如 List,Map 甚至是 Integer |
筛选与切片
- List<String> strings = Arrays.asList("Hello", "World");
- List<String> collect1 = strings.stream()
- // String 映射成 String[]
- .map(i -> i.split(""))
- // Arrays::Stream 数据数组, 返回一个流 String[]->Stream<String>
- // flatMap 各数组并不分别映射成一个流, 而是映射成流的内容 Stream<String>->Stream
- .flatMap(Arrays::stream)
- .collect(toList());
- System.out.println(collect);
-----> 输出 [H, e, l, l, o, W, o, r, l, d]
归约操作 reduce
- List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);
- // 有初始值的叠加操作
- Integer reduce = integers.stream().reduce(3, (i, j) -> i + j);
- Integer reduce2 = integers.stream().reduce(5, (x, y) -> x <y ? x : y);
- // 无初始值的叠加操作
- Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);
- // 无初始值的最大值
- Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);
- // 无初始值的最大值
- Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);
- // 求和
- Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);
reduce 做的事情是取两个数进行操作, 结果返回取下一个数操作, 以次类推.
Optional 是 java8 引入的新类, 避免造成空指针异常, 在集合为空时, 结果会包在 Optional 中, 可以用 isPresent() 方法来判断是否为空值.
无初始值的情况下可能为空, 故返回 Optional
中间
操作 | 类型 | 作用 | 函数描述 | 函数 |
---|---|---|---|---|
flatmap | 中间 | 使通过的流返回内容 | T -> boolean | Predicate |
终端
操作 | 类型 | 作用 |
---|---|---|
anyMatch | 终端 | 返回 boolean,判断是否有符合条件内容 |
noneMatch | 终端 | 返回 boolean,判断是否无符合条件内容 |
allMatch | 终端 | 返回 boolean,判断是全为符合条件内容 |
findAny | 终端 | Optional |
findFirst | 终端 | Optional |
reduce | 终端 | Optional |
数值流
包装类型的各种操作都会有拆箱操作和装箱操作, 严重影响性能. 所以 Java8 为我们提供了原始数值流.
- // 数值流求平均值
- OptionalDouble average = apples.stream()
- .mapToDouble(Apple::getPrice)
- .average();
- // 数值流求和
- OptionalDouble average = apples.stream()
- .mapToDouble(Apple::getPrice)
- .sum();
- // 数值流求最大值, 没有则返回 2
- double v = apples.stream()
- .mapToDouble(Apple::getPrice)
- .max().orElse(2);
- // 生成随机数
- IntStream s = IntStream.rangeClosed(1,100);
下面列表为常见数值流操作操作
中间
操作 | 类型 | 作用 |
---|---|---|
rangeClosed(1,100) | 中间 | 生成随机数 (1,100] |
range(1,100) | 中间 | 生成随机数(1,100) |
boxed() | 中间 | 包装成一般流 |
mapToObj | 中间 | 返回为对象流 |
mapToInt | 中间 | 映射为数值流 |
终端, 终端操作与 List 一般流类似
构建流
值创建
Stream<String> s = Stream.of("java","python");
数组创建
- int[] i = {
- 2,3,4,5
- };
- Stream<int> = Arrays.stream(i);
由文件生成, NIO API 已经更新, 以便利用 Stream API
Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
由函数创建流: 无限流
- // 迭代
- Stream.iterate(0,n->n+2)
- .limit(10)
- .forEach(System.out::println);
- // 生成, 需要传递实现 Supplier<T > 类型的 Lambda 提供的新值
- Stream.generate(Math.random)
- .limit(5)
- .forEach(System.out::println);
三, 总结
至此, 本文讲述了常见的流操作, 目前排序, 筛选, 求和, 归约等大多数操作我们都能实现了. 与过去相比, 操作集合变的简单多了, 代码也变的更加简练明了.
目前 Vert.x,Spring 新出的 webFlux 都通过 lambda 表达式来简化代码, 不久的将来, 非阻塞式框架的大行其道时, lambda 表达式必将变的更加重要!
至于开篇见到的分组!!! 下篇文章见~
参考资料:
Java8 实战, Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft http://product.dangdang.com/23952002.html
来源: https://www.cnblogs.com/cdream-zs/p/10504499.html