Java8实战-总结7
- 通过行为参数化传递代码
- 真实的例子
- 用Runnable执行代码块
- GUI事件处理
- 小结
- Lambda表达式
- Lambda管中窥豹
通过行为参数化传递代码
真实的例子
用Runnable执行代码块
线程就像是轻量级的进程:它们自己执行一个代码块。但是,怎么才能告诉线程要执行哪块代码呢?多个线程可能会运行不同的代码。需要一种方式来代表稍候执行的一段代码。在Java
里,可以使用Runnable
接口表示一个要执行的代码块。请注意,代码不会返回任何结果(即void):
// java.lang.Runnable
public interface Runnable {
public void run();
}
可以像下面这样,使用这个接口创建执行不同行为的线程:
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("Hello world");
}
});
用Lambda
表达式的话,看起来是这样:
Thread t = new Thread(() -> System.out.println("Hello world"));
GUI事件处理
GUI编程的一个典型模式就是执行一个操作来响应特定事件,如鼠标单击或在文字上悬停。
例如,如果用户单击“发送”按钮,之后显示一个弹出式窗口,或把行为记录在一个文件中。还是需要一种方法来应对变化;需要能够作出任意形式的响应。在JavaFX
中,可以使用EventHandler
,把它传给setOnAction
来表示对事件的响应:
Button button = new Button("Send");
button.setOnAction(new EventHandlercActionEvent>() {
public void handle(ActionEvent event) {
label.setText("Sent!!");
});
这里,setOnAction
方法的行为就用EventHandler
参数化了。用Lambda
表达式的话,看起来就是这样:
button.setOnAction((ActionEvent event) -> label.setText("Sent!!"));
小结
以下是本章中的关键概念。
- 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
- 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
- 传递代码,就是将新行为作为参数传递给方法。但在
Java 8
之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8
之前可以用匿名类来减少。 Java API
包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI
处理。
Lambda表达式
利用行为参数化来传递代码有助于应对不断变化的需求。它允许你定义一个代码块来表示一个行为,然后传递它。你可以决定在某一事件发生时(例如单击一个按钮)或在算法中的某个特定时刻(例如筛选算法中类似于“重量超过150克的苹果”的谓词,或排序中的自定义比较操作)运行该代码块。一般来说,利用这个概念,就可以编写更为灵活且可重复使用的代码了。
但使用匿名类来表示不同的行为并不令人满意:代码十分啰嗦,这会影响程序员在实践中使用行为参数化的积极性。Java 8中提供了解决这个问题的新工具——Lambda表达式。它可以让你很简洁地表示一个行为或传递代码。现在可以把Lambda
表达式看作匿名功能,它基本上就是没有声明名称的方法,但和匿名类一样,它也可以作为参数传递给一个方法。
如何构建Lambda
,它的使用场合,以及如何利用它使代码更简洁。还有一些新的东西,如类型推断和Java 8 API
中重要的新接口。最后,将介绍方法引用(method reference),这是一个常常和Lambda
表达式联用的有用的新功能。
Lambda管中窥豹
可以把Lambda
表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名——匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数——说它是函数,是因为
Lambda
函数不像方法那样属于某个特定的类。但和方法一样,Lambda
有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。 - 传递——
Lambda
表达式可以作为参数传递给方法或存储在变量中。 - 简洁——无需像匿名类那样写很多模板代码。
Lambda
这个词来自于学术界开发出来的一套用来描述计算的λ
演算法。在Java
中传递代码十分繁琐和冗长。Lambda
解决了这个问题:它可以让你十分简明地传递代码。理论上来说,在Java 8
之前做不了的事情,Lambda
也做不了。但是,现在用不着再用匿名类写一堆笨重的代码,来体验行为参数化的好处了!Lambda
表达式鼓励采用行为参数化风格。最终结果就是你的代码变得更清晰、更灵活。比如,利用Lambda
表达式,可以更为简洁地自定义一个Comparator
对象。
先前:
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());
代码看起来更清晰了。现在,请注意,基本上只传递了比较两个苹果重量所真正需要的代码。看起来就像是只传递了compare
方法的主体。甚至还可以进一步简化代码。
刚刚展示的Lambda
表达式有三个部分,如下图所示:
- 参数列表——这里它采用了
Comparator
中compare
方法的参数,两个Apple
。 - 箭头——箭头 -> 把参数列表与
Lambda
主体分隔开。 - Lambda主体——比较两个
Apple
的重量。表达式就是Lambda
的返回值了。
为了进一步说明,下面给出了Java 8
中五个有效的Lambda
表达式的例子。
//第一个Lambda表达式具有一个string类型的参数并返回一个int。Lambda没有return语句,因为已经隐含了return
(String s) -> s.length()
//第二个Lambda表达式有一个Apple类型的参数并返回一个boolean(苹果的重量是否超过150克)
(Apple a) -> a.getWeight() > 150
//第三个Lambda表达式具有两个int类型的参一数而没有返回值(void返回)。注意Lambda表达式可以包含多行语句,这里是两行
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x + y);
}
//第四个Lambda表达式没有参数,返回一个int
() -> 42
//第五个Lambda表达式具有两个apple类型的参数,返回一个int:比较两个Apple的重量
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight ())
Java
语言设计者选择这样的语法,是因为C#
和Scala
等语言中的类似功能广受欢迎。Lambda
的基本语法是
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements;}
可以看到,Lambda
表达式的语法很简单。
Lambda语法测验
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1)() -> {}
(2)() -> "Raoul"
(3)() -> {return "Mario";}
(4)(Integer i) -> return "Alan"+ i;
(5)(String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
(1)这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
(2)这个Lambda没有参数,并返回String作为表达式。
(3)这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:(Integer i) -> {return "Alan"+ i;}。
(5)“IronMan”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号和分号,如下所示:(String s) -> "IronMan"。或者使用显式返回语句,如下所示:(String s) -> {return "IronMan";}。
一些Lambda的例子和使用案例: