使用匿名类来表示不同的行为并不令人满意:代码十分的啰嗦,这会影响程序员在实践中使用行为参数化的积极性。在这里,我们会认识Java8中解决这个问题的工具Lambda表达式。它可以让你很简洁第表示一个行为或者传递代码。现在你可以把Lambda表达式看做匿名功能,它基本上没有声明名称的方法,但和匿名类一样,它可以作为参数传递给一个方法。
1.Lambda管中窥豹
我们可以把Lambda表达式理解为简洁地表示课传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有可以抛出的异常列表。这个定义够大的。让我们慢慢道来。
(1).Lambda的介绍
匿名--我们说,是因为它不像普通的方法那样有一个明确的名称:写的少而想的多!
函数--我们说它是函数,是因为Lambda函数不像方法那样属于某一个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能含有可以抛出的异常列表。
传递--Lambda表达式可以作为参数传递给方法或者存储在变量中。
简洁--无需像匿名类那样写很多的代码。
你是不是好奇Lambda这个词是从哪儿来的?其实它是来自学术界开发出来的一套用来描述计算的λ演算法。你为什么应该关心Lambda表达式呢?在这之前看到了,在Java中传递代码十分的繁琐和冗长。那么,现在有了好消息了!Lambda解决了这个问题:它可以让你的代码看上去十分的简明。理论上来说,你在Java8之前做不了的事情,Lambda。但是,你现在用不着再用匿名类写一堆笨重的代码,来体验行为参数化的好处吧!Lambda表达式鼓励你采用我们之前提到的行为参数化风格。最终的结果就是你的代码变得更加清晰、更加灵活。比如,利用Lambda表达式,你可以更为简洁地自定义一个Comparator对象了。
以前:
Comparator<Apple> byWeight = new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.compareTo(o2);
}
}
之后(使用Lambda表达式):
Comparator<Apple> byWeight = (Apple o1, Apple o2) -> o1.compareTo(o2);
不得不承认,代码看起来更加的清晰了!要是现在你觉得Lambda表达式看起来一头雾水的话也没关系,我们很快会一点一点的解释清楚的。现在,请注意你基本上只传递了比较两个比较重量所真正需要的代码。看起来就像是只传递了compare方法的主体。你很快就会学到,你甚至还可以进一步简化代码。
Lambda表达式有三个部分,如上图所示。
参数列表--这个它采用了Comparator中compare方法的参数,两个Apple。
箭头--箭头->把参数列表和Lambda主体分割开。
Lambda主体--比较两个Apple的重量。表达式Lambda的返回值了。
例如:
A.
(String s) -> s.length() // 一个String类型的参数并返回一个int。Lambda没有return语句,因为已经隐含了return。
B.
(Apple a) -> a.getWeight() > 150 //一个Apple类型的参数并返回一个boolean类型(苹果的重量是否大于150g)。
C.
(int x, int y) -> { //具有两个int类型的参数而没有返回值(void 返回值)。注意Lambda表达式可以包含多行语句,这里是两行。
System.out.println("result:");
System.out.println(x + y);
}
D.
() -> 42 //没有参数,并且返回一个int
(2).Lambda的语法
Java语言设计者选择这样的语法,是因为C#和Scala等语言中的类似功能广受欢迎。Lambda的基本语法是:
(parameters) -> expression
或者是(注意语句中的花括号)
(paramerers) -> { expression;}
注意:
带括号与不带括号有一定的区别:不带括号只能是一行语句,并且不能带return,末尾不能加分号;带括号可以是一行也可以是多行,但是如果函数有返回值的话,在末尾必须return,而且每一句后面必须带分号。例如:() -> "hello world" 等于 () -> { return "hello world";}
使用案例 | Lambda事例 |
---|---|
布尔表达式 | (List<String> list) -> list.isEmpty() |
创建对象 | ()-> new Apple() 或者是 () -> {return new Apple();} |
消费一个对象 | (Apple a) -> { System.out.println( a.getWeight() );} |
从一个对象中选择/抽取 | (String s) -> s.length() |
组合两个值 | (int a, int b) -> a * b |
比较两个对象 | (Apple a1, Apple a2) -> a1.compareTo(a2) |
2.在哪里以及如何使用Lambda
现在你可能在想,在哪里可以使用Lambda表达式。在上一个例子中,我们把Lambda赋给了一个Comparator<Apple> 类型的变量。我们在之前中实现filter方法中使用Lambda:
List<Apple> result = filterApple(inventory, (Apple apple) ->"red".equals(apple.getColor()));
那到底哪里可以使用Lambda呢?你可以在函数式接口上使用Lambda表达式。在之前的代码中,你可以把Lambda表达式作为第二个参数传递给filter方法,因为它这里需要Predicate<T>接口,而这个接口是一个函数式接口。如果听起来太抽象了,不要太担心了,现在我们就来详细地解释这是什么意思,以及函数式接口是什么。
(1).函数式接口
还记得我们在之前,为了参数化filter方法的行为而创建的Predicate<T> 接口吗? 它就是一个函数式接口,为什么呢?因为Predicate仅仅定义了一个抽象方法:
public interface ApplePredicate<T> {
boolean test(T t);
}
简而言之,函数式接口就是只定义了一个抽象方法的接口。你已经知道Java API的一些函数式接口,如我们在之前涉及到的Comparator和Runnable
注意:
接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供了默认实现的方法)。哪怕很很多的默认的方法,只要接口只定义了一个抽象方法,它就仍然是一个函数是接口。
用函数式接口可以干什么呢?Lambda表达式允许你可以直接以内联的形式为函数式接口的抽象方法提供了实现,并把整个表达式作为函数式接口的实例。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后在直接内联将它实例化。下面的代码是有效的,因为Runnable是一个只定义了一个抽象方法run的函数式接口:
A.使用Lambda
Runnable r1 = () -> System.out.println("Hello World");
B.使用匿名类
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
3.函数描述符
(1).Lambda的签名和函数式接口的抽象方法的签名
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。例如,Runnable接口可以看做一个什么也不接受什么也不返回(void)的函数的签名(签名表示为 () -> void ),因为他只有一个叫作run方法的抽象方法,这个方法什么也不接受,什么也不返回。
我们在之前使用了一个特殊的标识符来描述Lambda和函数式接口的签名。() -> void 代表参数列表为空,并且返回void的函数。这正是Runnable接口所代表的。例如:(Apple, Apple) -> int 代表接收两个Apple作为参数并且返回int的函数。
你可能在想,Lambda表达式是怎么做类型检查的。之后我们会知道, 编译器是如何检查Lambda在给定的上下文中是否有效。现在,我们只需要知道Lambda表达式可以被赋给一个变量,或者传递给一个接收函数式接口作为参数的方法就好了,当然这个Lambda表达式的签名要与函数式接口的抽象方法一样的。
public void process(Runnable r){
r.run();
}
process(() -> System.out.println("Hello World"))
上面的代码打印的是 “Hello World”。Lambda表达式 () -> System.out.println("Hello World")不接收参数并且返回为void。这个恰恰就是Runnable接口中run方法的签名。
(2).@FunctionalInterface注解
如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface的注解。这个注解用于表示该接口会设计成一个函数式接口。如果使用了@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误信息可能是:Multiple non-overriding abstract methods found in interface Foo,表明存在多个抽象方法。注意的是:@FunctionalInterface不是必需的,但是对于为此设计的接口而言,使用它是比较好的做法。它就好像是@Override注解表示方法被重写了