一、简介
Java8中两大重要改变。第一个是Lambda表达式,第二是Stream API。
Stream是Java8中处理的抽象概念,它可以对指定的集合进行操作,执行非常复杂的查找、过滤和映射等操作。
使用StreamAPI对集合数据进行操作,就类似于执行sql进行的数据库查询。StreamAPI提供了一种高效且易于使用的处理数据的方式。
二、Stream is what?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,流讲的是计算。
- Stream不会自己存储元素
- Stream不会改变元数据。相反,会返回一个持有结果的新Stream
- Stream操作是延迟执行
三、Stream操作三个步骤
- 创建Stream,通过数据源(数组、集合等)获取一个流
- 中间操作,对数据进行操作
- 终止操作,执行中间操作,并产生结果
四、Lambda表达式
在开始学习Stream之前,需要先了解一下Lambda表达式。
在了解Lambda表达式之前,需要知道什么是函数式接口。
函数式(Functional Interface)接口是Java8对一类特殊类型的接口的称呼。这类接口只定义了唯一的抽象方法的接口。对于函数式接口来说==@FunctionalInterface== 并不是必须的,只要在接口中只定义了唯一的抽象方法,它实质上就是一个函数式接口,可以用来实现Lambda表达。
在Java8中很出常数式接口都放在java.util.function下面,一般有以下四大核心接口:
接口类型 | 函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|---|
消费型接口 | Consumer《T》 | T | void | 对类型为T的对象应用操作。void accept(T t) |
供给型接口 | Supplier《T》 | 无 | T | 返回类型为T的对象。T get() |
函数型接口 | Function《T,R》 | T | R | 对类型为T的对象执行操作并返回R。R apply(T t) |
断言型接口 | Predicate《T》 | T | boolean | 确定T是否满足条件,返回布尔值。boolean test(T) |
Lambda表达式组成
三部分:
- 第一部分:形式参数,在一个括号内,多个用,分割
- 第二部分:标志符号,->
- 第三部分:方法体,可以是表达式和代码块
例如:
// 表达式
(parameters) -> expression
(int a,int b) -> return a + b; //求和
//代码块
(parameters) -> { statements; }
(int a) -> {System.out.println("a = " + a);} //打印,无返回值
(int a) -> {return a * a;} //求平方
Lambda表达式应用场景:
- 使用()->{}代替匿名类
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("no use lambda");
}
});
Thread t2 = new Thread(() -> System.out.println("use lambda"));
- 以流水线的方式处理数据(主要是通过.来不断调用stream方法)
// 实例
Map<Boolean, List<Integer>> listMap = integers.stream().collect(Collectors.groupingBy(i -> i % 2 == 0)); //根据奇偶性分组
- 数据并行处理
- 内部迭代取代外部迭代
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
System.out.println(feature); //外部迭代
}
List features = Arrays.asList("Lambdas", "Default Method", "Stream API",
"Date and Time API");
features.stream.forEach(n -> System.out.println(n)); //内部迭代
- 重构现有臃肿代码,提高开发效率
Lambda表达式的最佳实践
- 保持Lambda表达式简短和一目了然
- 使用@FunctionalInterface 注解
如果你确定了某个interface是用于Lambda表达式,请一定要加上@FunctionalInterface,表明你的意图。不然将来说不定某个不知情的家伙比如你旁边的好基友,在这个interface上面加了另外一个抽像方法时,你的代码就悲剧了。
- 优先使用java.util.function包下面的函数式接口
- 不要把Lambda表达式和匿名内部类同等对待
虽然我们可以用匿名内部类来实现Lambda表达式,也可以用Lambda表达式来替换内部类,但并不代表这两者是等价的。这两者在某一个重要概念是不同的:this指代的上下文是不一样的。当您使用内部类时,它将创建一个新的范围。通过实例化具有相同名称的新局部变量,可以从封闭范围覆盖局部变量。您还可以在内部类中使用这个关键字作为它实例的引用。但是,lambda表达式可以使用封闭范围。您不能在lambda的主体内覆盖范围内的变量.
private String value = "Enclosing scope value";
public String scopeExperiment() {
Foo fooIC = new Foo() {
String value = "Inner class value";
@Override
public String method(String string) {
return this.value;
}
};
String resultIC = fooIC.method("");
Foo fooLambda = parameter -> {
String value = "Lambda value";
return this.value;
};
String resultLambda = fooLambda.method("");
return "Results: resultIC = " + resultIC +
", resultLambda = " + resultLambda;
}
运行上面这段代码我们将到 resultIC = “Inner class value”,resultLambda = “Enclosing scope value”。也就是说在匿名内部类中this指的是自身的引用,在Lambda表达式中this指的是外部。
5. 多使用方法引用
// 指的是 ::,例如
System.out::prinln
- 尽量避免在Lambda的方法体中使用{}代码块
// 优先使用
Foo foo = parameter -> buildString(parameter);
private String buildString(String parameter) {
String result = "Something " + parameter;
//many lines of code
return result;
}
// 而不是
Foo foo = parameter -> { String result = "Something " + parameter;
//many lines of code
return result;
};
- 不要盲目的开启并行流。
Lambda的并行流虽好,但也要注意使用场景。如果平常的业务处理比如过滤,提取数据,没有涉及特别大的数据和耗时操作,则真的不需要开启并行流。我在工作中看到有些人一个只有几十个元素的列表的过滤操作也开启了并行流,其实这样做会更慢。因为多行线程的开启和同步这些花费的时间往往比你真实的处理时间要多很多。但一些耗时的操作比如I/O访问,DB查询,远程调用,这些如果可以并行的话,则开启并行流是可提升很大性能的。因为并行流的底层原理是fork/join,如果你的数据分块不是很好切分,也不建议开启并行流。举个例子ArrayList的Stream可以开启并行流,而LinkedList则不建议,因为LinkedList每次做数据切分要遍历整个链表,这本身就已经很浪费性能,而ArrayList则不会。
五、Stream API
五-1、中间操作
多个中间操作可以连起来形成一个流水线,除非流水线上触发终止操作 ,否则中间操作不会执行。而在触发终止操作时,一次性全部处理,称为“惰性求值“。
5.1.1 筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 过滤 |
distinct() | 通过对象重写的hascode和equals去重 |
limit(long maxSize) | 截断,获取指定数量元素 |
skip(long n) | 跳过,与limit(n))互补 |
5.1.2 映射
方法 | 描述 |
---|---|
map(Function f) | 接受一个函数,应用到每个元素,并将其映射成一个新的元素 |
flatMap(Function f) | 接受一个函数,将流中的每一个值换成另一个流,然后把所有的流连接成一个流 |
/**
* 测试map跟flatMap的区别
* 有点跟集合中的add跟addAll方法类似
* add是将无论是元素还是集合,整体加到其中一个集合中去[1,2,3.[2,3]]
* addAll是将无论是元素还是集合,都是将元素加到另一个集合中去。[1,2,3,2,3]
*/
5.1.3 排序
方法 | 描述 |
---|---|
sorted() | 自然顺序排序 |
sorted(Comparator comp) | 自定义比较器 |
五-2、终止操作
终止操作会从流水线产生结果。其结果可以是List、Integer甚至是void
5.2.1查找与匹配
方法 | 描述 |
---|---|
allMatch(Predcate p) | 检查是否全部匹配 |
anyMatch(Predcate p) | 。。。 |
nontMatch(Predcate p) | … |
findFirst() | 返回第一个元素 |
findAny() | 返回任意元素 |
count() | 统计 |
max(Comparator c) | 返回最大 |
forEach(Consumer c) | 内部迭代,取代外部跌打 |
5.2.2 规约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperate b) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduce(BinaryOperate b) | 同上,返回Optional《T》 |
5.2.3 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式,接收一个Collector接口的实现,用于给流中的元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体请参阅文档。
六、参考代码
https://github.com/zhaoteng8069/springboot-demo.git
翻阅资料
https://www.jianshu.com/p/8e3b8a483bd8
https://www.cnblogs.com/linlinismine/p/9283532.html