《JAVA8开发指南》第二章采用Lambda表达式(一)
采用Lambda表达式
本章,你将学习到如何采用JAVA8的重要特性Lambda表达式。首先,你要了解“行为参数”这种模式。该模式能够使你写出来的代码适应需求变化。然后,你将看到该模式如何使得Lambda表达式的使用与以往比变得更加简洁。然后,你将学习如何精确地定位Lambda表达式的使用场景和使用方式。你也将了解JAVA8的另一个特性-方法参数,它能使你的代码更简洁更易读。带着所有这些新知识实战一个重构代码的例子。最后,你也将学习到如何使用Lambda表达式和方法参数。
为什么使用Lambda表达式
将Lambda表达式引入JAVA中的动机源于一个叫“行为参数”的模式。这种模式能够解决需求变化带来的问题,使代码变得更加灵活。在JAVA8之前,参数模式十分啰嗦。Lambda表达式通过精简的方式使用行为模式克服了这个缺点。举个例子,如果你需要找大于一定金额的发票。你可能写这样一个indInvoicesGreaterThanAmount方法:
List findInvoicesGreaterThanAmount(List invoi
ces, double amount) {
List result = new ArrayList<>();
for(Invoice inv: invoices) {
if(inv.getAmount() > amount) {
result.add(inv);
}
}
return result;
}
这个方法确实足够简单。可是如果你还需要找小于一定金额的发票呢?或者更糟糕的是,你需要从一个指定商户中找发票,并且也需要找一定数额呢?这时你需要一个方式来参数化指定条件下的过滤行为。下面我们将定义一个接口InvoicePredicate来描述条件,使用该接口重构上面的方法。
接口定义
interface InvoicePredicate {
boolean test(invoice inv);
}
重构方法
List<Invoice> findInvoices(List<Invoice> invoices, InvoicePredi
cate p) {
List<Invoice> result = new ArrayList<>();
for(Invoice inv: invoices) {
if(p.test(inv)) {
result.add(inv);
}
}
return result;
}
使用这段代码,你能够通过增加一个Invoice对象解决需求变化带来的问题。你只需要创建一个不同的InvoicePredicate 对象,将其传入方法findInvoices中。换句话说,你已经参数化了findInvoices行为。不好的是,使用这个新方法引入了额外的冗余,来看下面的代码:
List<Invoice> expensiveInvoicesFromOracle
= findInvoices(invoices, new InvoicePredicate() {
public test(Invoice inv) {
return inv.getAmount() > 10_000
&& inv.getCustomer() == Customer.ORACLE;
}
});
换句话说,代码变得更灵活的同时可读性变差了。最理想的状态是,代码灵活性和可读性兼备。Lambda表达式的引入能够做到这一点。通过使用它重构上面的代码如下:
List<Invoice> expensiveInvoicesFromOracle
= findInvoices(invoices, inv ->
inv.getAmount() > 10_000
&& inv.getCustomer() ==
Customer.ORACLE);
Lambda表达式的定义
现在知道了为什么需要在代码中引入Lambda表达式,也是时候精准的了解下Lambda表达式的定义。简单的来讲,lambda表达式是一个能够被传递的匿名函数,我们仔细看看这个定义:
匿名
Lambda表达式是匿名的,因为它没有像普通方法那样有一个明确的名称。它有点儿像匿名类,因为两者都没有明确的名称。
函数
一个 Lambda表达式像一个方法,它包含一串参数、一个体、一个返回类型和一串可能抛出的错误。和方法不同的是,它没有被声明为特殊类的一部分。
传递
一个 Lambda表达式能够作为一个方法的参数被传递,也能够作为一个结果被返回。
Lambda表达式语法
在写lambda表达式之前,需要知道它的语法。在本书中已经出现过lambda表达式:
Runnable r = () -> System.out.println("Hi");
FileFilter isXml = (File f) -> f.getName().endsWith(".xml");
这两个lambda表达式有三个部分:
- 一串参数,比如 (File f)
- 一个有两个字符组成的箭头:- 和 >
- 一个体,比如f.getName().endsWith(".xml")
lambda表达式有两种形式。当你的lambda表达式体中包含一个语句的时候可以采用第一种:
(parameters) -> expression
当你的lambda表达式体中包含一个或多个语句的时候可以使用第二种形。式请注意,你必须使用大括号将表达式包含进来:
(parameters) -> { statements;}
大体来说,如果lambda表达式参数的类型被间接的指出过,可以去掉类型声明。另外,如果参数个数只是一个,那么圆括号也可以被去掉。
Lambda表达式使用场景
现在,了解了如何写一个lambda表达式,接下来的问题是考虑使用lambda表达式的使用方式和使用场景。简单的说,你可以在函数接口中使用lambda表达式。函数接口里包含一个抽象方法。比如上文的两个lambda表达式。
Runnable r = () -> System.out.println("Hi");
FileFilter isXml = (File f) ->
f.getName().endsWith(".xml");
Runnable就是一个函数接口,因为它包含了一个抽象方法run。FileFilter也是一个函数接口,因为它也定义了一个抽象方法叫accept。
@FunctionalInterface
public interface Runnable {
void run();
}
@FunctionalInterface
public interface FileFilter {
boolean accept(File pathname);
lambda表达式重要的一点就是让你创建函数接口的实例。lambda表达式的体提供了函数接口中单个抽象方法的实现。因此,下面使用匿名类和lamba表达式实现的Runnable的结果是一直的。
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hi!");
}
};
r1.run();
Runnable r2 = () -> System.out.println("Hi!");
r2.run();
最后更新:2017-05-19 14:08:10