Lambda表达式
Lambda概述
lambda表达式没有名称,但是它有参数列表,函数体和返回类型,还可能包含一个异常列表。
- 匿名,lambda表达式不像方法一样有一个显式的名字
- 函数,lambda表达式不像方法一样关联到一个特定的类
- 传递,lambda表达式可以像函数参数一样传递,或者保存到变量里面
- 简洁,不需要像匿名类一样写很多样板代码
lambda基本语法有2种
(parameters) -> expression和
(parameters) -> { statements; }| 用例 | lambda示例 |
|---|---|
| 布尔表达式 | (List<String> list) -> list.isEmpty() |
| 创建对象 | () -> new Apple(10) |
| 消费对象 | (Apple a) -> { System.out.println(a.getWeight()); } |
| 从对象中提取属性 | (String s) -> s.length() |
| 合并两个值 | (int a, int b) -> a * b |
| 比较两个对象 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |
哪里可以使用lambda
你可以在函数接口上下文中使用lambda表达式
函数接口
简单地说,函数接口就是只有一个抽象方法的接口,比如:
public interface Predicate<T> {
boolean test (T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
public interface Runnable {
void run();
}
public interface ActionListener extends EventListener {
void actionPerformed(ActionEvent e);
}
public interface Callable<V> {
V call() throws Exception;
}
public interface PrivilegedAction<T> {
T run();
}注意,接口现在可以有默认方法。如果一个接口有许多默认方法,只要它只有一个抽象方法,那么它仍然是一个函数接口。
lambda表达式可以为函数接口的抽象方法提供实现,并将整个表达式看做函数接口的一个实例。
函数描述符
函数接口的抽象方法的签名描述了lambda表达式的签名,我们称这个抽象方法为函数描述符。我们使用一个特殊的标记来描述函数描述符,比如() -> void表示参数为空并返回void的函数。
只要lambda表达式具有与函数接口的抽象方法相同的签名,就可以将lambda表达式赋值给变量或传递给参数为函数接口的方法。
下面这个lambda表达式合法:() -> System.out.println(“This is awesome”);
System.out.println不是一个表达式,为什么不需要用花括号括起来? 原来,在Java语言规范中定义了一个用于void方法调用的特殊规则,void方法调用不需要使用花括号。
@FunctionalInterface用于指示一个接口是函数接口。如果使用@FunctionalInterface定义一个不是函数接口的接口,编译器会返回一个警告。@FunctionalInterface不是强制性的,但是使用它定义函数接口是个好习惯。
使用函数接口
Predicate<T>
java.util.function.Predicate<T>定义如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}Consumer<T>
java.util.function.Consumer<T>定义如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}Function<T, R>
java.util.function.Function<T, R>定义如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}Java 8同样添加了特殊版的函数接口以避免基础类型的autoboxing:
public interface IntPredicate {
boolean test(int t);
}常用函数接口表
| 函数接口 | 函数描述符 | 特殊版本函数接口 |
|---|---|---|
| Predicate<T> | T -> boolean | IntPredicate LongPredicate DoublePredicate |
| Consumer<T> | T -> void | IntConsumer LongConsumer DoubleConsumer |
| Function<T, R> | T -> R | IntFunction<R> IntToDoubleFunction IntToLongFunction LongFunction<R> LongToDoubleFunction LongToIntFunction DoubleFunction<R> DoubleToIntFunction DoubleToLongFunction ToIntFunction<T> ToDoubleFunction<T> ToLongFunction<T> |
| Supplier<T> | () -> T | BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
| UnaryOperator<T> | T -> T | IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
| BinaryOperator<T> | (T, T) -> T | IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
| BiPredicate<T, U> | (T, U) -> boolean | |
| BiConsumer<T, U> | (T, U) -> void | ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
| BiFunction<T, U, R> | (T, U) -> R | ToIntBiFunction<T, U> ToLongBiFunction<T, U> ToDoubleBiFunction<T, U> |
注意这些函数接口都不允许抛出checked异常。如果你需要lambda表达式抛出异常,你可以定义自己的函数接口声明checked异常,或者在lambda表达式函数体中catch异常。
类型检查,类型推断与限制
类型检查
lambda表达式的类型是从使用lambda表达式的上下文中推导出来的。lambda表达式上下文中期待的类型称为目标类型。
同样的lambda不同的函数接口
由于目标类型的原因,如果不同的函数接口具有兼容的抽象方法签名,相同的lambda表达式可以关联到它们。
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());特殊的void兼容规则
如果lambda函数体是一个表达式语句,则它和返回void的函数描述符兼容。比如:
// Predicate has a boolean return
Predicate<String> p = (String s) -> list.add(s);
// Consumer has a void return
Consumer<String> b = (String s) -> list.add(s);
类型推断
Java编译器可以推断出lambda表达式关联的函数接口,意味着它也能够推导出相应的抽象函数的签名。好处是Java编译器知道lambda表达式的参数类型,因此lambda表达式可以忽略掉参数类型:
List<Apple> greenApples = filter(inventory, apple -> GREEN.equals(apple.getColor()));
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());使用局部变量
lambda表达式允许使用自由变量(不是参数且定义在外部作用域),就像匿名类一样。
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);Lambda可以不受限制地捕获实例变量和静态变量。但是当捕获局部变量时,必须显式地声明它们为final或有效的final。Lambda表达式可以捕获只分赋值一次的局部变量。下面的代码将编译不了:
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;局部变量的限制
实例变量和局部变量在实现方式上有一个关键区别。实例变量存储在堆上,而局部变量存储在堆栈上。如果lambda可以直接访问局部变量,并且lambda在线程中使用,那么使用lambda的线程可以在分配变量的线程释放变量之后尝试访问该变量。因此Java将对自由变量的访问实现为对它的副本的访问,而不是对原始变量的访问。
方法引用
方法引用可以看作是只调用特定方法的lambda表达式的简写,可以将方法引用看作lambda的语法糖。
| Lambda | 等价方法引用 |
|---|---|
| (Apple apple) -> apple.getWeight() | Apple::getWeight |
| () -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
| (str, i) -> str.substring(i) | String::substring |
| (String s) -> System.out.println(s) | System.out::println |
| (String s) -> this.isValidName(s) | this::isValidName |
构造方法引用
如下图:

构造函数引用
使用类名和关键字new创建构造函数引用:ClassName::new,类似静态方法引用。比如,如果有不带参数的构造函数
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();等价于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();如果有带一个参数的构造函数,则满足Function函数接口:
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);如果有带亮个参数的构造函数,则满足BiFunction函数接口:
BiFunction<Color, Integer, Apple> c3 = Apple::new;
Apple a3 = c3.apply(GREEN, 110);组合lambda表达式的有用方法
组合Comparators
静态方法Comparator.comparing返回一个基于函数的Comparator,该函数提取一个关键字进行比较,如下所示:
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);反向比较
reversed方法反转指定Comparator的顺序:
inventory.sort(comparing(Apple::getWeight).reversed());链接Comparators
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));组合Predicates
谓词接口包含三个方法,可以重用现有的谓词来创建更复杂的谓词:negate、and和or。
Predicate<Apple> notRedApple = redApple.negate();
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150).or(apple -> GREEN.equals(a.getColor()));注意方法的优先级是从左到右。
组合Functions
Function接口提供2个方法用于组合lambda表达式:andThen和compose。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); // In mathematics you’d write g(f(x))
Function<Integer, Integer> j = f.compose(g); // In mathematics you’d write f(g(x))
int result1 = h.apply(1);
int result2 = j.apply(1);