java的Lambda表达式

作者 uunnfly 日期 2019-04-09
java的Lambda表达式

Lambda表达式是java8的新特性,允许把函数作为一个方法的参数,使代码更加简洁。
语法:(argument) -> (body)
如:

1
2
3
(arg1, arg2...) -> { body }

(type1 arg1, type2 arg2...) -> { body }

不需要函数声明,以及函数名。(写法跟js中的Lambda基本是一样的)
下面是几个例子:

1
2
3
4
5
(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

  • 如果函数内的语句只有一句可以省略大括号与return,返回值就是语句的值
  • 可以没有参数
  • 只有一个参数时可以没有小括号
  • 参数的类型可以不写,由编译器推导

下面看一下Lambda有什么用

代替匿名内部类

Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RunnableTest {
public static void main(String[] args) {

System.out.println("=== RunnableTest ===");

// Anonymous Runnable
Runnable r1 = new Runnable(){

@Override
public void run(){
System.out.println("Hello world one!");
}
};

// Lambda Runnable
Runnable r2 = () -> System.out.println("Hello world two!");

// Run em!
r1.run();
r2.run();

}
}

Lamdba省略了new Runnable()接口名和run()方法名,全部交给编译器推导
之前我们在新建一个线程时会使用匿名内部类,现在使用Lamdba简洁又高效

Comparator

在下面的例子中,一个ArrayList包含Person类,对其进行排序:

1
2
3
4
5
6
7
8
9
public class Person {
private String givenName;
private String surName;
private int age;
private Gender gender;
private String eMail;
private String phone;
private String address;
}

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
public class ComparatorTest {

public static void main(String[] args) {

List<Person> personList = Person.createShortList();

// Sort with Inner Class
Collections.sort(personList, new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.getSurName().compareTo(p2.getSurName());
}
});

System.out.println("=== Sorted Asc SurName ===");
for (Person p : personList) {
p.printName();
}

// Use Lambda instead

// Print Asc
System.out.println("=== Sorted Asc SurName ===");
Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));

for (Person p : personList) {
p.printName();
}

// Print Desc
System.out.println("=== Sorted Desc SurName ===");
Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));

for (Person p : personList) {
p.printName();
}

}
}

注:java.util.List也有sort的api,可以作为Collection.sort的替代品

Lambda的类型

Lambda的类型是什么?有些语言将Lambda看作对象,java为了保持旧版本向后兼容性,并没有这么做。
java.util.function中,定义了多种函数式接口。以下是比较常用的:
函数式接口

示例代码:

1
2
Consumer a = (s) -> System.out.println("test" + s);
a.accept("test");

函数接口有 3 条重要法则:

  1. 一个函数接口只有一个抽象方法。
  2. 在 Object 类中属于公共方法的抽象方法不会被视为单一抽象方法。
  3. 函数接口可以有默认方法和静态方法。

任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 Runnable 和 Callable 等传统接口,以及我们自己构建的自定义接口。

自定义函数接口

要创建自己的函数接口,需要做两件事:

  1. 使用 @FunctionalInterface 注释该接口,这是 Java 8 对自定义函数接口的约定。
  2. 确保该接口只有一个抽象方法。

作为一个示例,我们将创建一个 Order 类,它有一系列 OrderItem 以及一个转换并输出它们的方法。我们首先创建一个接口。

下面的代码将创建一个 Transformer 函数接口。

@FunctionalInterface
public interface Transformer {
T transform(T input);
}
该接口用 @FunctionalInterface 注释做了标记,表明它是一个函数接口。因为该注释包含在 java.lang 包中,所以没有必要导入。该接口有一个名为 transform 的方法,后者接受一个参数化为 T 类型的对象,并返回一个相同类型的转换后对象。转换的语义将由该接口的实现来决定。

这是 OrderItem 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrderItem {
private final int id;
private final int price;

public OrderItem(int theId, int thePrice) {
id = theId;
price = thePrice;
}

public int getId() { return id; }
public int getPrice() { return price; }

public String toString() { return String.format("id: %d price: %d", id, price); }
}

OrderItem 是一个简单的类,它有两个属性:id 和 price,以及一个 toString 方法。

现在来看看 Order 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.*;
import java.util.stream.Stream;

public class Order {
List<OrderItem> items;

public Order(List<OrderItem> orderItems) {
items = orderItems;
}

public void transformAndPrint(
Transformer<Stream<OrderItem>> transformOrderItems) {

transformOrderItems.transform(items.stream())
.forEach(System.out::println);
}
}

transformAndPrint 方法接受 Transform 作为参数,调用 transform 方法来转换属于 Order 实例的订单项,然后按转换后的顺序输出这些订单项。

这是一个使用该方法的样本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;
import static java.util.Comparator.comparing;
import java.util.stream.Stream;
import java.util.function.*;

class Sample {
public static void main(String[] args) {
Order order = new Order(Arrays.asList(
new OrderItem(1, 1225),
new OrderItem(2, 983),
new OrderItem(3, 1554)
));


order.transformAndPrint(new Transformer<Stream<OrderItem>>() {
public Stream<OrderItem> transform(Stream<OrderItem> orderItems) {
return orderItems.sorted(comparing(OrderItem::getPrice));
}
});
}
}

我们传递一个匿名内部类作为 transformAndPrint 方法的参数。在 transform 方法内,调用给定流的 sorted 方法,这会对订单项进行排序。这是我们的代码的输出,其中显示了按价格升序排列的订单项:

id: 2 price: 983
id: 1 price: 1225
id: 3 price: 1554

方法引用 ::

方法引用通过方法的名字来指向一个方法。它使用一对冒号 ::

1
2
3
4
5
6
7
8
9
10
11
12
//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));

//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

使用双冒号来调用System.out类的静态方法println,或者调用某个对象的方法

streams

Java 8 增加了一些超棒的流 APIs。java.util.stream.Stream 接口包含许多有用的方法,能结合 Lambda 表达式产生神奇的效果。
我们将 Lambda 表达式 x -> x*x 传给 map() 方法,该方法会作用于流中的所有元素。之后,我们使用 forEach 方法打印数据中的所有元素:

1
2
3
4
5
6
7
8
9
10
//Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
int x = n * n;
System.out.println(x);
}

//New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);

你可以访问下面的地址详细了解streams
Java 8 中的 Streams API 详解

参考链接

菜鸟教程-Java 8 Lambda 表达式
Java SE 8: Lambda Quick Start
深入浅出 Java 8 Lambda 表达式
为什么完美的 lambda 表达式只有一行
JDK8函数式接口Function、Consumer、Predicate、Supplier
Java 8 习惯用语,第 7 部分 函数接口
Java8:Lambda表达式增强版Comparator和排序