Lambda表达式
理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码。
基本语法
(parameters) -> expression
(表达式)
(parameters) -> { statements; }
(语句,注意语句的花括号)
- 例如:
-
() -> {}
这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。 -
() -> "Raoul"
这个Lambda没有参数,并返回String作为表达式(Lambda没有return语句,因为已经隐含了return)。 -
() -> {return "Mario";}
这个Lambda没有参数,并返回String(利用显式返回语句)。
- 使用案例
使用案例 | 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表达式函数式接口就是只定义一个抽象方法的接口
Java API中本来就已经有的一些函数式接口
例如:public interface Comparator<T> { int compare(T o1, T o2); } public interface Runnable{ void run(); }
所以函数式接口可以干什么呢?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。(Runnable是一个只定义了一个抽象方法run的函数式接口)
Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例!
使用Lambda:
Runnable r1 = () -> System.out.println("Hello World 1");
使用匿名类:
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World 2");
}
};
-
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。() -> void代表参数列表为空,且返回void的函数。这正是Runnable接口所代表的。
那么Lambad如何做类型检查呢?
Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
- 找出filter方法的声明。
- 要求它是Predicate<Apple>(目标类型)对象的第二个正式参数。
- Predicate<Apple>是一个函数式接口,定义了一个叫作test的抽象方法。
- test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean。
- filter的任何实际参数都必须匹配这个要求。
注意:如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句也必须与之匹配。
可以粗略的理解为Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样。
类型推断
Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。
没有类型推断:
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
有类型推断:
Comparator<Apple> c =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
-
使用局部变量
Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。
原因:
- 实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
- 这一限制不鼓励你使用改变外部变量的典型命令式编程模式
-
双冒号运算符:
Lambda及其等效方法引用的例子:
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() |
Apple::getWeight |
() -> Thread.currentThread().dumpStack() |
Thread.currentThread()::dumpStack |
(String s) -> System.out.println(s) |
System.out::println |
构建方法引用
- 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
- 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。
- 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:ClassName::new
---摘自《Java8实战》
网友评论