一、什么是Lambda表达式
上一篇中已经看到了Lambda表达式的使用场景,这一篇深入学习一下Java8的这个新特性。
首先Lambda说一下表达式的定义:它是一个简洁的可以用于传递的匿名函数。 包含以下一些特性:
1- 它是匿名的: 它不像不通的方法那样有一个明确的名称。
2- 它是一个函数: 因为它不属于某个特定的类,但是它有参数列表、函数体、返回类型、和异常列表。
3- 它可以被传递: 可以当成参数传递给一个方法,或者存储在一个变量当中。
4- 它是简洁的:不需要像匿名类那样写很多冗余的代码。
Lambda表达式的结构:
没有Lambda表达式 之前写一个匿名类是这样写的:
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
有了Lambda表达式 之后:
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Lambda表达式的基本语法有两种形式
一种表达式语法,不带花挂号 :(parameters) -> expression
表达式最后一句就是返回值,不需要显示的return语句。
或者
一种是语句语法,带有花挂号:(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表达式。
那么什么是函数式接口呢? 简要的说函数式接口就是有且只有一个抽象方法的接口.
三、函数描述器
定义:函数描述器是函数接口的抽象方法的方法签名,从本质上描述Lambda表达式的签名,我们称这种抽象方法为函数描述器。
举例来说,Runnable接口就可以被描述为一个函数接口的签名,它没有参数没有返回值。
Java 8 中通用的函数没描述器
函数接口 | 函数描述器 | 基本类型函数接口 |
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>, 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<L, R> | (L, R) -> 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> |
由于java 中基本类型和包装类型 之间可以自动实现相互转化,叫做boxing 和unboxing。
但是这个过程会带来性能的开销。所以在java8 中提供了基本类型的功能接口。
四、类型检查、类型推断、约束
Lambda表达式没有明确的类型说明, 它的类型会根据它所在的上下文推断。
Figure 3.4. Deconstructing the type-checking process of a lambda expression
相同的Lambda表达式,不同的功能接口。
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
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 返回值可以兼容
// Predicate has a boolean return
Predicate<String> p = s -> list.add(s);
// Consumer has a void return
Consumer<String> b = s -> list.add(s);
类型推断
约束
lambda表达式可以 没有约束的捕获实例变量和静态变量,但是捕获局部变量时,被捕获的局部变量必须是final类型的或者effectively final的 (定义为final 或者 事实上的final类型),并且lambda表达式不能改变局部变量的值。
五、方法引用
方法引用可以让你重复使用现有的方法定义,并且像Lambda表达式一样传递他们,而且比lambda表达式更加简洁。
//引用普通方法
Converter<String, Integer> converter = Integer::valueOf;
//引用构造方法
PersonFactory<Person> personFactory = Person::new;
六、 Lambda表达式访问变量的作用域
public void testLambdaScope(){
//访问局部变量
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
String converted = stringConverter.convert(2); // 3
System.out.println(converted); // 3
//访问成员变量
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
//访问静态变量
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
//访问接口的 默认方法 是不行的
Formula formula = (a) -> sqrt( a * 100);
}