Java 8 中最重要的两个东西 Lambda 表达式与 Stream API 。
Lambda表达式相信大家都很熟悉了,在日常编码中也经常会用到。所谓温故而知新,本文就是通过对知识点的复习进行整理出来的。希望可以帮到需要的小伙伴。
Stream 流操作,经常和 Lambda 一同使用,下面我们来逐一讲解。
1、Lambda
1.1 何为 Lambda 表达式?
Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);
可以写出更简洁、更灵活的代码;
作为一种更紧凑的代码风格,是Java语言表达能力得到提升。
1.2 Lambda 表达式如何使用?
1.2.1 基础语法
操作符:->
表达式左侧:参数列表,例如: 无参形式,直接使用 () -> {}
表达式右侧:执行代码块/Lambda 体
首先,我们来看一个例子,体验一下 Lambda表达式。
在没有 Lambda 之前,比如我们最熟悉的 Comparator 比较两个值的大小的匿名内部类:
@Test
public void test01(){
//匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
@Override
public boolean equals(Object obj) {
return false;
}
};
//调用
TreeSet<Integer> set = new TreeSet<>(comparator);
}
当使用了 Lambda 表达式之后,我们可以写成下面这种样子:
@Test
public void test02(){
// Lambda 表达式
Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
TreeSet<Integer> set = new TreeSet<>(comparator);
}
怎么样?代码看起来是不是舒服多了 !由原来的一坨,变成了一行,非常清晰,这就是 Lambda 表达式的魅力。
下面,我们一起来学习一下 Lambda 表达式的其他语法及用法。
1.2.2 Lambad表达式的几种常用格式
- 无参数,无返回值
例如 Runnable 接口
public class Test02 {
int num = 10; //jdk 1.7以前 必须final修饰
@Test
public void test01(){
//匿名内部类
new Runnable() {
@Override
public void run() {
//在局部类中引用同级局部变量
//只读
System.out.println("Hello World" + num);
}
};
}
@Test
public void test02(){
//语法糖
Runnable runnable = () -> {
System.out.println("Hello Lambda");
};
}
}
- 有一个参数,无返回值
@Test public void test03(){ Consumer<String> consumer = (a) -> System.out.println(a); consumer.accept("Lambda 一个参数,无返回值写法测试"); } /** * 只有一个参数的时候,表达式左侧的小括号,可以不写 */ @Test public void test03(){ Consumer<String> consumer = a -> System.out.println(a); consumer.accept("我觉得还行!"); }
- 有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句
@Test public void test04(){ Comparator<Integer> comparator = (a, b) -> { System.out.println("比较接口"); return Integer.compare(a, b); }; }
- 有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)
@Test public void test04(){ Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b); }
注意:Lambda 表达式 参数的数据类型可以省略不写 Jvm可以自动进行 “类型推断”
1.2.3 案例
调用 Collections.sort() 方法,通过定制排序 比较两个 Employee (先按照年龄比,年龄相同按照姓名比),使用 Lambda 表达式作为参数传递。
- 定义实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Employee { private Integer id; private String name; private Integer age; private Double salary; }
- 定义 List 传入数据
List<Employee> emps = Arrays.asList( new Employee(101, "Z3", 19, 9999.99), new Employee(102, "L4", 20, 7777.77), new Employee(103, "W5", 35, 6666.66), new Employee(104, "Tom", 44, 1111.11), new Employee(105, "Jerry", 60, 4444.44) );
-
测试
@Test public void test01(){ Collections.sort(emps, (e1, e2) -> { if (e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); } else { return Integer.compare(e1.getAge(), e2.getAge()); } }); emps.forEach(System.out::println); }
2、Java8 内置四大核心函数式接口
2.1 什么是函数式接口?
接口中只有一个抽象方法的接口,使用 @FunctionalIterface 注解修饰
我们通过使用自定义的函数式接口来配合 Lambda 表达式写一个例子。
- 定义一个函数式接口
@FunctionalInterface public interface MyFun { /** * 两个整数操作,操作类型由用户决定 */ Integer operate(Integer a, Integer b); }
- 具体的使用
public Integer operation(Integer a, Integer b, MyFun myFun) { return myFun.operate(a, b); } @Test public void test() { Integer result = operation(1, 2, (x, y) -> x + y); System.out.println(result); }
上面的例子,是我们自己定义的,那么后续当使用 Lambda 表达式的时候,是不是每次都需要去自定义一个类似的函数式接口呢?大家不用操作,Java8 已经内置了 4 个核心函数接口,我们来逐一看一下。
2.2 内置函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer 消费型接口 | T | void | 对类型为T的对象应用操作:void accept(T t) |
Supplier 提供型接口 | 无 | T | 返回类型为T的对象:T get() |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t) |
Predicate 断言型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t) |
2.2.1 消费型接口
@Test
public void test01(){
//Consumer
Consumer<Integer> consumer = (x) -> System.out.println("消费型接口" + x);
//test
consumer.accept(100);
}
2.2.2 提供型接口
@Test
public void test02(){
List<Integer> list = new ArrayList<>();
List<Integer> integers = Arrays.asList(1,2,3);
list.addAll(integers);
//Supplier<T>
Supplier<Integer> supplier = () -> (int)(Math.random() * 10);
list.add(supplier.get());
System.out.println(supplier);
for (Integer integer : list) {
System.out.println(integer);
}
}
2.2.3 函数型接口
@Test
public void test03(){
//Function<T, R>
String oldStr = "abc123456xyz";
Function<String, String> function = (s) -> s.substring(1, s.length()-1);
//test
System.out.println(function.apply(oldStr));
}
2.2.4 断言型接口
@Test
public void test04(){
//Predicate<T>
Integer age = 35;
Predicate<Integer> predicate = (i) -> i >= 35;
if (predicate.test(age)){
System.out.println("你该退休了");
} else {
System.out.println("我觉得还OK啦");
}
}
2.2.5 其他接口
在开发工作中,合理运用 Lambda 表达式,可以有效提高我们代码的可读性及美观性,这也是官方建议使用的方式。
3、 Stream API
3.1 什么是 Stream?
流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,流讲的是计算。
注意:
Stream 自己不会存储元素。
Stream 不会改变源对象。相反,他们会发挥一个持有结果的新 Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3.2 Stream 操作
3.2.1 创建 Stream
一个数据源(如:集合、数组)获取一个流。
/**
* 创建流
*/
@Test
public void test01(){
/**
* 集合流
* - Collection.stream() 串行流
* - Collection.parallelStream() 并行流
*/
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//数组流
//Arrays.stream(array)
String[] strings = new String[10];
Stream<String> stream2 = Arrays.stream(strings);
//Stream 静态方法
//Stream.of(...)
Stream<Integer> stream3 = Stream.of(1, 2, 3);
//无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
stream4.forEach(System.out::println);
//生成
Stream.generate(() -> Math.random())
.limit(5)
.forEach(System.out::println);
}
3.2.2 中间操作
一个中间操作链,对数据源的数据进行处理。
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
- 筛选与切片
方法 | 描述 |
filter | 接收 Lambda ,从流中排除某些元素 |
limit | 截断流,使其元素不超过给定数量 |
skip | 跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补 |
distinct | 筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素 |
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test01(){
emps.stream()
.filter((x) -> x.getAge() > 35)
.limit(3) //短路?达到满足不再内部迭代
.distinct()
.skip(1)
.forEach(System.out::println);
}
- 映射
方法 | 描述 |
map | 接收 Lambda ,将元素转换为其他形式或提取信息; 接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
flatMap | 接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流 |
@Test
public void test02(){
List<String> list = Arrays.asList("a", "b", "c");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
}
flatMap:
public Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
@Test
public void test03(){
List<String> list = Arrays.asList("a", "b", "c");
Test02 test02 = new Test02();
list.stream()
.flatMap(test02::filterCharacter)
.forEach(System.out::println);
}
- 排序
方法 | 描述 |
sorted() | 自然排序 |
sorted(Comparator c) | 定制排序 |
Comparable:自然排序
@Test
public void test04(){
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
.sorted() //comparaTo()
.forEach(System.out::println);
}
Comparator:定制排序
@Test
public void test05(){
emps.stream()
.sorted((e1, e2) -> { //compara()
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else {
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
- 查找、匹配 (终止操作)
方法 | 描述 |
allMatch | 检查是否匹配所有元素 |
anyMatch | 检查是否至少匹配一个元素 |
noneMatch | 检查是否没有匹配所有元素 |
findFirst | 返回第一个元素 |
findAny | 返回当前流中的任意元素 |
count | 返回流中元素的总个数 |
max | 返回流中最大值 |
min | 返回流中最小值 |
public enum Status {
FREE, BUSY, VOCATION;
}
@Test
public void test01(){
List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);
boolean flag1 = list.stream()
.allMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag1);
boolean flag2 = list.stream()
.anyMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag2);
boolean flag3 = list.stream()
.noneMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag3);
// 避免空指针异常
Optional<Status> op1 = list.stream()
.findFirst();
// 如果Optional为空 找一个替代的对象
Status s1 = op1.orElse(Status.BUSY);
System.out.println(s1);
Optional<Status> op2 = list.stream()
.findAny();
System.out.println(op2);
long count = list.stream()
.count();
System.out.println(count);
}
- 归约、收集
方法 | 描述 |
归约: reduce(T identity, BinaryOperator) / reduce(BinaryOperator) | 可以将流中的数据反复结合起来,得到一个值 |
收集:collect | 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法 |
reduce:
/**
* Java:
* - reduce:需提供默认值(初始值)
* Kotlin:
* - fold:不需要默认值(初始值)
* - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer integer = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(integer);
}
3.2.3 终止操作
- 收集 collect
方法 | 描述 |
collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream 中元素做汇总的方法 |
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test02(){
//放入List
List<String> list = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
//放入Set
Set<String> set = emps.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
//放入LinkedHashSet
LinkedHashSet<String> linkedHashSet = emps.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(LinkedHashSet::new));
linkedHashSet.forEach(System.out::println);
}
@Test
public void test03(){
//总数
Long count = emps.stream()
.collect(Collectors.counting());
System.out.println(count);
//平均值
Double avg = emps.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//总和
Double sum = emps.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
//最大值
Optional<Employee> max = emps.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
//最小值
Optional<Double> min = emps.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
@Test
public void test04(){
//分组
Map<Integer, List<Employee>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getId));
System.out.println(map);
//多级分组
Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
.collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
if (e.getAge() > 35) {
return "开除";
} else {
return "继续加班";
}
})));
System.out.println(mapMap);
//分区
Map<Boolean, List<Employee>> listMap = emps.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
System.out.println(listMap);
}
@Test
public void test05(){
//总结
DoubleSummaryStatistics dss = emps.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getMax());
System.out.println(dss.getMin());
System.out.println(dss.getSum());
System.out.println(dss.getCount());
System.out.println(dss.getAverage());
//连接
String str = emps.stream()
.map(Employee::getName)
.collect(Collectors.joining("-")); //可传入分隔符
System.out.println(str);
}
- case案例
案例一:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?(如:给定【1,2,3,4,5】,返回【1,4,9,16,25】)
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
.map((x) -> x * x)
.forEach(System.out::println);
}
案例二:怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test02(){
Optional<Integer> result = emps.stream()
.map((e) -> 1)
.reduce(Integer::sum);
System.out.println(result.get());