创建并使用 Lambda 表达式
什么是 lambda 表达式?
- 传统
List<Car> compactCars = findCars(cars,
new Searchable() {
public boolean test(Car car) {
return car.getType().equals(
CarTypes.COMPACT);
}
});
- Java8引入了一种符号,可以:
List compactCars = findCars(cars,
(Car c) ->
c.getType().equals(CarTypes.COMPACT)
);
使用 lambdas,允许你使用一种更简单的方式来编程,这种方式叫做函数式编程,这是一种不同于面向对象程序设计的范式
Lambda 表达式的组成
(Object arg1, int arg2) -> arg1.equals(arg2);
参数列表
- 一个 lambda 表达式可以有零个,一个或多个参数:
() -> System.out.println("Hi");
(String s) -> System.out.println(s);
(String s1, String s2) -> System.out.println(s1 + s2);
- 参数的类型可以显式声明,也可以从上下文中推断出来
(s) -> System.out.println(s);
- 如果只有一个参数,类型就会被推断出来,并且不强制使用括号
s -> System.out.println(s);
// This doesn't compile
String s = ""; s -> System.out.println(s);
箭头->
用于分离参数和主体代码块
代码块
- Lambda 表达式的主体可以包含一个或多个语句
- 如果正文有一个语句,则不需要大括号,并返回表达式的值(如果有的话)
() -> 4; (int a) -> a*6;
- 如果主体包含多个语句,则需要使用大括号,如果表达式返回一个值,则必须返回一个 return 语句
() -> {
System.out.println("Hi");
return 4;
}
(int a) -> {
System.out.println(a);
return a*6;
}
- 如果 lambda 表达式没有返回结果,返回语句是可选的,和void方法一样,使用return表示退出
() -> System.out.println("Hi");
() -> {
System.out.println("Hi");
return;
}
函数式接口和Lambda表达式关系
- 函数接口的抽象方法的签名提供了 lambda 表达式的签名(这个签名称为函数描述符)。要使用 lambda 表达式,首先需要一个函数接口
interface Searchable {
boolean test(Car car);
}
- 可以创建一个 lambda 表达式,以 Car 对象作为参数并返回一个布尔值
Searchable s = (Car c) -> c.getCostUSD() > 20000;
编译器推断 lambda 表达式可以被分配给 Searchable 接口,仅仅通过它的签名。lambda 表达式并不包含它们正在实现的函数接口的信息。表达式的类型是从使用 lambda 的上下文中推导出来的。当 lambda 表达式被分配给 Searchable 接口时,lambda 必须获取其抽象方法的签名。
- 使用 lambda 作为方法的参数,编译器将使用方法的定义来推断表达式的类型
class Test {
public void find() {
find(c -> c.getCostUSD() > 20000);
}
private void find(Searchable s) {
// Here goes the implementation
}
}
- 如果相同的 lambda 表达式具有兼容的抽象方法签名,那么它们可以与不同的函数接口关联。例如:
interface Searchable {
boolean test(Car car);
}
interface Saleable {
boolean approve(Car car);
}
//...
Searchable s1 = c -> c.getCostUSD() > 20000;
Saleable s2 = c -> c.getCostUSD() > 20000;
- Lambda 表达式是一个函数。这和我们知道的方法有点不同,因为它实际上是和一个数学函数相关的。
- 一个数学函数接受一些输入并产生一些输出,只要我们用相同的参数调用它,它总是返回相同的结果。
- 可以将 lambda 表达式看作匿名方法(或函数) ,因为它们没有名称,就像匿名类一样
Lambda 表达式与匿名类
表达式是匿名类的一个替代选择,但它们是不一样的
相似
- 局部变量(方法中定义的变量或参数)只有在声明为最终变量或实际为最终变量时才能使用
- 可以访问封闭类的实例或静态变量
- 它们不能抛出比函数接口方法的 throws 子句中指定的更多检查过的异常。只有相同的类型或子类型
区别
- 对于匿名类,this关键字解析为匿名类本身。对于lambda表达式,它解析为写入lambda的封闭类。
- 不能从lambda表达式中访问函数接口的默认方法。匿名类可以。
- 由于不需要加载另一个类,lambda表达式更有效。
- 匿名类被编译成内部类。但是lambda表达式被转换成其封闭类的私有静态(在某些情况下)方法,并使用invokedynamic指令(在java7中添加)对它们进行动态绑定。
为什么局部变量必须为final
为什么局部变量必须是最终的,而实例或者静态变量在匿名类或者 lambda 表达式中使用时不是这样,这是因为这些变量在 Java 中的实现方式。
实例变量存储在堆(计算机内存中的任何地方都可以访问的区域)上,而局部变量存储在堆栈(计算机内存中存储每个函数创建的临时变量的特殊区域)上。
堆上的变量由多个线程共享,但堆栈上的变量隐式地局限于它们所在的线程。
因此,当您创建匿名内部类或 lambda 表达式的实例时,局部变量的值将被复制。换句话说,您不是使用变量,而是使用复制的值。
首先,这可以防止线程相关的问题。其次,由于您使用的是一个副本,如果变量可以被方法的其余部分修改,那么您将使用一个过时的变量。
final定义的(实际上是最后一个变量)消除了这些可能性。由于该值不能更改,不必担心这些更改是可见的,或者会导致线程问题。
总结
- Lambda表达式有三个部分:参数列表、箭头和正文:(Object o)->System.out.println(o)
- 您可以将lambda表达式视为匿名方法(或函数),因为它们没有名称。
- lambda表达式可以有零(用空括号表示)、一个或多个参数。
- 参数的类型可以显式声明,也可以从上下文推断。
- 如果只有一个参数,则类型是推断的,并且不必使用括号。
- 如果lambda表达式使用与封闭上下文的变量名相同的参数名,则会生成编译错误。
- 如果主体有一条语句,则不需要大括号,并返回表达式的值(如果有)。
- 如果主体有多条语句,则需要大括号,如果表达式返回值,则必须返回一条return语句。
- 如果lambda表达式不返回结果,则返回语句是可选的。
- 函数接口的抽象方法的签名提供lambda表达式的签名(该签名称为函数描述符)。
- 要使用lambda表达式,首先需要一个函数接口。但是,lambda表达式不包含有关实现哪个函数接口的信息。
- 表达式的类型是从使用lambda的上下文中推导出来的。这种类型称为目标类型。
- 可以推断lambda表达式的目标类型的上下文包括赋值、方法或构造函数参数以及强制转换表达式。
- 与匿名类一样,lambda表达式可以访问实例和静态变量,但只能访问final或有效的final局部变量。
- 不能抛出未在函数接口方法的throws子句中定义的异常。
- lambda表达式,它解析为写入lambda的封闭类。
- 不能从lambda表达式中访问函数接口的默认方法。
Comments | 0 条评论