0%

Java8特性lambda表达式笔记

前言

本文涉及以下知识:

  1. Lambda表达式基本概念
  2. Lambda表达式语法
  3. Lambda表达式实战:java.util.function
  4. Stream 流的概念
  5. Stream实战

Lambda基本概念

  Java8的一个新特性就是引入了函数式接口。通常我们的方法的入参都是基本数据类型,或者是实例对象的引用,这在一定程度上限制了方法的多样性,因此Java引入了函数式编程,即在声明(构造器或者方法)时把一个函数接口当成入参。一般这样的入参在实际使用时,会用一个实现了该接口的类对象来传入,这种对象我们一般会写成匿名的,如下所示(声明一个线程):

1
2
3
4
5
6
7
// 接口Runnable作为声明时的入参,在使用时使用一个匿名类来传入
Thread td = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
});

  这种匿名函数写起来很笨重,因此引入了Lambda表达式来简化匿名函数的定义,如果用Lambda表达式来声明上述td对象,则可以写成如下形式(两种写法的作用是完全一致的):

1
Thread td = new Thread(() -> System.out.println("run"));

  可以说,labmda表达式就是一种简洁的匿名类书写方式,用来增强代码的简洁性和可读性。

Lambda表达式语法

Lambda使用的条件

  使用Lambda表达式来实现某个接口时,必须保证该接口只有一个抽象方法。如果该接口有两个或者两个以上的抽象方法,则无法使用Lambda。这里需要注意一点:因为Java8为接口加入了defaultstatic方法,这两种方法不是抽象方法,是可以使用lambda的,如下例所示:

  • 可以使用Lambda的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
// 只有一个抽象方法,可以用使用lambda
public interface InterA {

void canUseLambda();

default void log() {
System.out.println("log...");
}

static void perf() {
System.out.println("perf...");
}
}
1
InterA interA = () -> System.out.println("canUseLambda"); // 编译通过
  • 不可以使用Lambda的情况
1
2
3
4
public interface InterB {
int canUseLambda();
int cantUseLambda();
}

会报如下的错误:

Lambda 语法

  Lambda语法很简单,完整写法就是(参数列表) -> {具体实现代码(有返回类型可以写return)}。举个例子,假设有一个Rectangle接口,里面有一个抽象方法,接收两个参数,返回一个double;同时也有一个Circle接口,里面有一个方法,接收一个参数,返回一个double

1
2
3
4
5
6
7
public interface Rectangle {
double cal(double x, double y);
}

public interface Circle {
double cal(double x);
}
  • 完整定义的方法
1
2
3
4
5
6
7
Rectangle rect1 = (x, y) -> {
return x * y;
};

Circle circle1 = (x) -> {
return Math.sqrt(x);
};
  • 简化写法
1
2
3
4
5
6
7
8
// 当具体实现代码只有一行的时候,不需要写大括号,如果有return,则省去return
Rectangle rect2 = (x, y) -> x * y

// 如果入参只有一个,则入参的()也可以省略
Circle circle2 = x -> Math.sqrt(x);

// 如果参数只有一个,而且调用的方法也只需要传入这个参数
Circle circle3 = Math::sqrt;
  • 使用
1
2
3
4
5
rect1.cal(4.0,5.0); // 20.0
rect2.cal(4.0, 5.0); // 20.0
circle1.cal(81.0); // 9.0
circle2.cal(81.0); // 9.0
circle3.cal(81.0); // 9.0

Lambda实战: java.util.function

  Lambda主要用在函数式编程里面,而Java8引入了一个新的包用来支持函数式编程,那就是java.util.fuction

java.util.function常用接口

接口文档可查看:java.util.function接口文档

接口 方法 解释
Consumer<T> void accept(T t) 表示”消费者”,接收一个参数(一般用消费t来实现某个功能)
Supplier<T> T get() 表示”生产者”,不接收参数,返回一个值
Predicate<T> bool test(T t) 预测接口,提供一个参数,返回一个bool
Function<T, R> R apply(T t) 接收一个参数t,返回一个值

  这里写个简单的例子,这些接口一般会经常用在Stream里,下一章会介绍这些方法的具体应用,这里写个简单的,方便理解。下列方法一个重要的理解方式就是:入参函数使用了另一个入参作为了参数,类方法又调用了入参函数。

  • 定义一个类,使用上述接口作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DoLambda {
public <T> T doSupply(Supplier<T> supplier){
return supplier.get();
}

public <T> void doConsumer(T t, Consumer<T> consumer) {
consumer.accept(t);
}

public <T> boolean doPredicate(T t, Predicate<T> predicate) {
return predicate.test(t);
}

public <T, R> R doFunction(T t, Function<T, R> function) {
return function.apply(t);
}
}
  • 使用这些方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
DoLambda d = new DoLambda();
System.out.println(d.doSupply(() -> "生产者提供的一个字符串"));
d.doConsumer("消费者要消费这个字符串", System.out::println);
d.doPredicate("判断这个字符串的长度是否为10", x -> x.length() == 10);
boolean flag = d.doPredicate("判断这个字符串的长度是否为10", x -> x.length() == 10);
System.out.println(flag);
Double a = d.doFunction(53.4, x -> {
System.out.print("计算53.4的平方: ");
return Math.pow(x, 2);
});
System.out.println(a);
}

/******输出如下*******/
生产者提供的一个字符串
消费者要消费这个字符串
false
计算53.4的平方: 2851.56

Stream API

  StreamAPI也是Java8引入的新特性,可以实现集合串行或者并行的流操作。这是Java8最实用的功能。这里说一个重要的,流只是操作,不改变原集合。

  • 创建流
1
2
Stream<T> stream1 = 集合<T>对象.stream() // 串行流
Stream<T> stream2 = 集合<T>对象.parallelStream() // 并行流
1
2
3
4
5
6
7
8
9
10
public class Test04 {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("yi", 45)); // name, age
list.add(new Person("two", 11));
list.add(new Person("three", 56));
list.add(new Person("four", 27));
Stream<Person> stream = list.stream();
}
}
  • 部分流操作
流操作 功能
void forEach(Consumer<? super T> action) 迭代这个集合中的每一个数据
Stream<R> map(Function<? super T, ? extends R> mapper) 把集合中的每一个T类型数据映射成R类型数据,返回一个R类型的流
Stream<T> filter(Predicate<? super T> predicate) 对集合中每一个数据进行筛选,筛出符合条件的数据(true),并返回一个流
Stream<T> limit(long maxSize) 获取指定数量的流,数量为maxSize(串行有序,并行无序)
Optional<T> reduce(BinaryOperator<T> accumulator) 累加器,两两相加,最后返回一个Optional容器(Optional是为了防止空指针,用容器对象.get()得到T,关于BinaryOperator去看文档
T reduce(T identity, BinaryOperator<T> accumulator) 有初始值的累加,因为有初始值,所以不会出现空指针,直接返回T
Stream<T> sorted(Comparator<? super T> comparator) 对集合类进行排序,传入一个比较器,返回一个流对象
Object[] toArray() 和 collect(Collectors.toList()) 对流进行数组或集合类型的转化

Stream实战

以上写了一个Person的列表,要求用流分别实现如下功能:

  1. 将人的按照年龄大小排序并打印

  2. 筛选出年龄小于50的并打印

  3. 打印出总年龄

  4. 打印前3个人

  5. 把每个人的年龄加100,并打印

  6. 将所有的人映射到一个Map集合里面,key是名字,value是年龄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
System.out.println("====问题1====");
list.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(Person::print);
System.out.println("====问题2====");
list.stream().filter(p -> p.getAge() < 50).forEach(Person::print);
System.out.println("====问题3====");
System.out.println(list.stream().mapToInt(Person::getAge).reduce(Integer::sum).orElse(-1));
System.out.println("====问题4====");
list.stream().limit(3).forEach(Person::print);
System.out.println("====问题5====");
list.forEach(p -> p.setAge(p.getAge() + 100));
list.forEach(Person::print);
System.out.println("====问题6====");
Map<String, Integer> map = list.stream().collect(Collectors.toMap(Person::getName, Person::getAge));
for(String key: map.keySet()) {
System.out.println("姓名:" + key + " 年龄:" + map.get(key));
}

// 输出结果
====问题1====
姓名:two 年龄:11
姓名:four 年龄:27
姓名:yi 年龄:45
姓名:three 年龄:56
====问题2====
姓名:yi 年龄:45
姓名:two 年龄:11
姓名:four 年龄:27
====问题3====
139
====问题4====
姓名:yi 年龄:45
姓名:two 年龄:11
姓名:three 年龄:56
====问题5====
姓名:yi 年龄:145
姓名:two 年龄:111
姓名:three 年龄:156
姓名:four 年龄:127
====问题6====(顺序是随机的)
姓名:yi 年龄:145
姓名:four 年龄:127
姓名:three 年龄:156
姓名:two 年龄:111

总结

  本章内容主要介绍了一下Lambda表达式,同时稍微介绍了一点StreamAPI的知识,这部分知识不难,主要难在架构设计,希望以后我忘记这部分内容的时候能够通过这篇文章回想起来。