一、前言

《java8中Stream使用总结》 中我们已经多次使用Lambda表达式,感受到其中的巧妙之处,简化了匿名函数的表达方式。Lambda表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

二、Lambda表达式解析

Lambda表达式的基本语法如下:

1
2
3
(parameters) -> expression
or
(parameters) -> { statements; }

由语法可以看到,Lambda表达式包含了三个部分:

参数列表;

箭头->把参数列表与Lambda主体分隔开;

Lambda主体,只有一行代码的时候可以省略大括号和return关键字。

比如下面这些Lambda表达式都是合法的:

1
2
3
4
5
6
7
8
9
10
11
12
        (String str) -> str.length();
(String str) -> { return str.length(); }

() -> System.out.println("hello");

() -> {}
() -> 17

(int x, int y) -> {
System.out.println(x);
System.out.println(y);
}

三、Lambda的使用场合

什么时候可以使用Lambda表达式?使用Lambda必须满足以下两个条件:

实现的对象是函数式接口的抽象方法;

函数式接口的抽象方法的函数描述符和Lambda表达式的函数描述符一致。

3.1 函数式接口

函数式接口的定义开头已经说了,这里就不再赘述。在Java 8之前,常见的函数式接口有java.util.Comparator,java.lang.Runnable等。拿java.util.Runnable来说,查看其源码如下:

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

这个接口只有一个抽象方法,并且使用@FunctionalInterface注解标注。

接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。

3.2 函数描述符

函数描述符其实也可以理解为方法的签名。比如上述的Runnable抽象方法不接受参数,并且返回void,所以其函数描述符为() -> void。而() -> System.out.println("hello")Lambda表达式也是不接受参数,并且返回void,即其函数描述符也是() -> void。所以代码Runnable r = () -> System.out.println("hello");是合法的。

特殊的void兼容规则:如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。例如,以下Lambda是合法的,尽管List的add方法返回了一个 boolean,而不是Runnable抽象方法函数描述符() -> void所要求的void:

1
2
List<String> list = new ArrayList<>();
Runnable r = () -> list.add("hello");

四、更简洁的Lambda

编写一个类型转换的函数式接口:

1
2
3
4
@FunctionalInterface
public interface TransForm<T, R> {
R transForm(T t);
}

编写一个Lambda表达式实现该函数式接口,用于实现String转换为Integer,代码如下:

1
2
TransForm<String, Integer> t = (String str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));

上面的Lambda表达式可以进一步简化为如下方式:

1
2
TransForm<String, Integer> t = (str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));

因为Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名。就拿这个例子来说,TransForm的抽象方法transForm在本例中的函数描述符为(String) -> Integer,所以对应的Lambda的签名也是如此,即Lambda的参数即使不声名类型,Java编译器可以知道其参数实际上为String类型。

其实,上面的Labmda表达式还不是最简洁的,其还可以更进一步地简化为如下写法:

1
2
TransForm<String, Integer> t = Integer::valueOf;
System.out.println(t.transForm("123"));

你肯定很困惑,这还是Lambda表达式吗,箭头去哪里了?双冒号又是什么鬼?其实这种写法有一个新的名称,叫做方法的引用。

方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它,这样代码可读性更好。基本写法就是目标引用放在分隔符::前,方法的名称放在后面。

举几个Lambda及其等效方法引用的例子:

Lambda表达式等效方法引用
(String s) -> System.out.println(s)System.out::println
(str, i) -> str.substring(i)String::substring
() -> Thread.currentThread().dumpStack()Thread.currentThread()::dumpStack

符号::除了出现在方法的引用外,它还常见于构造函数的引用中。为了演示什么是构造函数的引用,我们创建一个新的函数式接口:

1
2
3
4
@FunctionalInterface
public interface Generator<T, R> {
R create(T t);
}

创建一个Apple类:

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
public class Apple {

private String color;

private Double weight;

}

创造一些苹果

1
2
3
4
5
6
7
        List<Apple> appleList = new ArrayList<>();
appleList.add(new Apple("red", 0.4));
appleList.add(new Apple("red", 0.6));
appleList.add(new Apple("red", 1.3));
appleList.add(new Apple("green", 0.2));
appleList.add(new Apple("green", 0.35));
appleList.add(new Apple("green", 1.1));

四、Lambda表达式实战

假如现在有如下需求:现有一个包含了各种颜色不同重量的苹果的List,编写一个方法,从中筛选出满足要求的苹果。比如筛选出红色的苹果、红色并且重量大于1kg的苹果、绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果等等。

4.1 不使用Lambda

在没有接触Lambda之前,我们一般会这样做:

定义一个筛选的接口

1
2
3
public interface AppleFilter {
boolean test(Apple apple);
}

然后根据筛选的条件来编写各个不同的实现类:

筛选出红色苹果的实现方法:

1
2
3
4
5
6
public class RedApple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return "red".equalsIgnoreCase(apple.getColor());
}
}

筛选出红色并且重量大于1kg的苹果的实现方法:

1
2
3
4
5
6
public class RedAndMoreThan1kgApple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0;
}
}

筛选出绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果的实现方法:

1
2
3
4
5
6
7
public class GreenAndLessThan05OrRedAndMoreThan05Apple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5)
|| ("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5);
}
}

筛选苹果的方法:

1
2
3
4
5
6
7
8
9
10
11
public class AppleFilterMethod {
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter) {
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list) {
if (filter.test(apple)) {
filterList.add(apple);
}
}
return filterList;
}
}

开始筛选苹果:

1
2
3
4
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList, new RedApple());
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}

输出:

red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3

可以看到,我们为了满足各种筛选条件创造了各种筛选接口的实现类,真正起作用的只有筛选方法中return那一行代码,剩下的都是一些重复的模板代码。使用Java 8中的Lambda可以很好的消除这些模板代码。

4.2 使用Lambda

AppleFilter接口实际上就是一个函数式接口,所以它的各种实现可以用Lambda表达式来替代,而无需真正的去写实现方法。

定义筛选接口:

1
2
3
public interface AppleFilter {
boolean test(Apple apple);
}

筛选苹果的方法:

1
2
3
4
5
6
7
8
9
10
11
public class AppleFilterMethod {
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter) {
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list) {
if (filter.test(apple)) {
filterList.add(apple);
}
}
return filterList;
}
}

接下来便可以开始筛选了:

筛选红色的苹果:

1
2
3
4
5
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> "red".equalsIgnoreCase(apple.getColor()));
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}

输出:

red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3

筛选出红色并且重量大于1kg的苹果:

1
2
3
4
5
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0);
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}

输出:

red apple,weight:1.3

筛选出绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果:

1
2
3
4
5
6
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5) ||
("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5));
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}

输出:

red apple,weight:0.6
red apple,weight:1.3
green apple,weight:0.2
green apple,weight:0.35

使用Lambda表达式消除了大量的样板代码,并且可以灵活的构造筛选条件!

五、Java8中的函数式接口

下表列出了Java8中常见的函数式接口:

函数式接口函数描述符原始类型特化
PredicateT->booleanIntPredicate,LongPredicate, DoublePredicate
ConsumerT->voidIntConsumer,LongConsumer, DoubleConsumer
Function<T,R>T->RIntFunction, IntToDoubleFunction, IntToLongFunction,LongFunction, LongToDoubleFunction,LongToIntFunction,DoubleFunction, ToIntFunction, ToDoubleFunction, ToLongFunction
Supplier()->TBooleanSupplier,IntSupplier, LongSupplier,DoubleSupplier
UnaryOperatorT->TIntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator(T,T)->TIntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L,R>(L,R)->boolean
BiConsumer<T,U>(T,U)->voidObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer
BiFunction<T,U,R>(T,U)->RToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U>

5.1 Predicate

predicate: 英 [ˈpredɪkət] 美 [ˈpredɪkət] 断言,断定的意思。从接口的名称就可以推断出这个函数式接口的主要作用就是用于判断作用,Predicate源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

可看到java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean,函数描述符为(T) -> boolean举几个例子:

1
2
3
4
5
6
7
// 偶数判断
Predicate<Integer> isEven = (in) -> in % 2 == 0;
isEven.test(17); // false

// 判断字符串的长度是否为0
Predicate<String> isEmptyString = String::isEmpty;
isEmptyString.test(""); // true

除了抽象方法外,java.util.function.Predicate接口还定义了三个默认方法:and,negate和or,对应“与”,“非”和“或”操作,这样我们便可以复合Lambda表达式了,比如:

1
2
3
4
5
6
7
8
// 判断是偶数,并且大于30
Predicate<Integer> isEven = (in) -> in % 2 == 0;
isEven.and((in) -> in > 30).test(40); // true

// 奇数判断
Predicate<Integer> isEven = (in) -> in % 2 == 0;
Predicate<Integer> isOdd = isEven.negate();
isOdd.test(17); // true

5.2 Consumer

英 [kənˈsju:mə(r)] 美 [kənˈsu:mə(r)] n.消费者。该函数式接口用于消费一个对象,即接收一个对象,对其执行某些操作,然后没有返回值。Consumer源码如下所示:

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

可看到java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void),函数描述符为(T) -> void。其还提供了一个默认方法andThen。举个例子:

1
2
3
4
Consumer<Apple> printAppleColor = (a)-> System.out.println(a.getColor());
printAppleColor.accept(new Apple("red",17)); // red

printAppleColor.andThen((a) -> System.out.println(a.getWeight())).accept(new Apple("red", 17)); // red 17.0

5.3 Supplier

supplier 英 [səˈplaɪə(r)] 美 [səˈplaɪər] n.供应商;供应者;供给者。其源码如下:

1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get();
}

可看到java.util.function.Supplier定义了一个名叫get的抽象方法,它不接收参数,返回泛型T的对象,函数描述符为() -> T。举个例子:

1
2
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person

5.4 Functions

Functions源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
return t -> t;
}
}

java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象,函数描述符为(T) -> R。举个例子:

1
2
3
4
Function<Apple, Double> getAppleWeight = (a) -> {
return a.getWeight();
};
getAppleWeight.apply(new Apple(17)); // 17.0

Functions接口还提供了两个抽象方法compose和andThen,从源码可以看出两者的根本区别。举个compose例子:

1
2
3
Function<Integer, Integer> f = (x) -> x + 1;
Function<Integer, Integer> g = (x) -> x * 2;
f.compose(g).apply(2); // 5

过程为:f(g(2)),也就是1+(2*2)。

举个andThen的例子:

1
2
3
Function<Integer, Integer> f = (x) -> x + 1;
Function<Integer, Integer> g = (x) -> x * 2;
f.andThen(g).apply(2); // 6

过程为:g(f(2)),也就是(2+1)*2。

5.5 原始类型特化

在学习Function接口的时候,我们定义了f函数:

1
Function<Integer, Integer> f = (x) -> x + 1;

x的类型为Integer类型,1为int类型,返回值为Integer类型,整个过程实际上为Integer.valueOf(x.intValue() + 1)。虽然编译器可以自动帮我们完成拆装箱,但这会造成不必要的性能消耗。考虑到了这一点,Java8为我们提供了int类型的Function接口:IntFunction:

1
2
3
4
@FunctionalInterface
public interface IntFunction<R> {
R apply(int value);
}

所以f最好重构为:

1
IntFunction<Integer> f = (x) -> x + 1;

剩余的原始类型特化函数式接口可参考上面的表格。

六、Java8中增强的Comparator

在Java8之前,Comparator接口用于实现简单的比较排序算法。比如有如下List:

1
2
3
4
5
6
List<Double> list = new ArrayList<>();
list.add(12.3);
list.add(100.2);
list.add(3.14);
list.add(27.7);
list.add(-9.8);

使用Comparator接口对其从小到大排序:

1
2
3
4
5
6
Collections.sort(list, new Comparator<Double>() {
@Override
public int compare(Double o1, Double o2) {
return o1.compareTo(o2);
}
});

Comparator接口也是一个函数式接口,函数描述符为(T,T) -> int,Java8中可以使用Lambda改造上面的排序方法:

1
Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

Java8对List提供了sort方法,可以替代Collections.sort,所以上面的代码可以简化为:

1
list.sort((o1, o2) -> o1.compareTo(o2));

使用方法的引用来进一步简化:

1
list.sort(Double::compareTo);

Java8对Comparator进行了增强,加入了一些实用的默认方法,比如对排序结果反转:

1
2
Comparator<Double> comparator = Double::compareTo;
list.sort(comparator.reversed());

更多方法可以参考Comparator接口的JavaDoc。

注意:查看Comparator的时候发现其虽然是函数式接口,但是却包含了compare和equals这两个抽象方法,顿时有点懵逼,函数式接口不是只能有一个抽象方法么?查找资料后发现:函数式接口中可以额外定义多个抽象方法,但这些抽象方法签名必须和Object的public方法一样,接口最终有确定的类实现,而类的最终父类是Object。因此函数式接口可以定义Object的public方法。