利用行为参数化来传递代码有助于应对不断变化的需求。它允许你定义一个代码块来表示一个行为,然后传递它。你可以决定在某一个事件发生时(例如单机一个按钮)或在算法中的某个特定时刻运行该代码。一般来说,利用这个概念,你就可以编写更为灵活且可重复使用的代码了。
但你也看到,使用匿名类来表示不同的行为并不令人满意:代码十分啰嗦,这会影响程序员在实践中使用行为参数化的积极性。在本章中,我们会教给你Java8中解决这个问题的新工具一一Lambda表达式。它可以让你很简洁地表示一个行为或传递代码。现在你可以把Lambda表达式看做匿名功能,它基本上就是没有声明名称的方法,但和匿名类一样,它也可以作为参数传递给一个方法。
我们会展示如何构建Lambda,它的使用场合,以及如何利用它使代码更简洁。我们还会接收一些新的东西,如类型推断和Java8 API中重要的新接口。最后,我们将介绍方法引用(method reference),这是一个常常和Lambda表达式联用的有用的新功能。
本章的行文思想就是教你如何一步一步地写出更简洁、更灵活的代码。
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常参数列表。这个定义够大了,让我们慢慢道来。
匿名一一我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想的多。函数一一我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。传递一一Lambda表达式可以作为参数传递给方法或存储在变量中。简洁一一无需像匿名类那样写很多模板代码。
Lambda的基本语法是:
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; }
下面例举两个错误的写法:
(Integer i) -> return “Alan” + 1;
(String s) -> { “IronMan”; }
- return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:(Integer i) -> { return “Alam” + 1; }。“Iron Man” 是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号和分号,如下所示:(String s) -> “Iron Man”。或者如果你喜欢,可以使用显示返回语句,如下所示:(String s) -> { return “IronMan”; }
为了参数化filter方法的行为而创建的Predicate接口,它就是一个函数式接口!为什么呢?因为Predicate仅仅定义了一个抽象方法:
public interface Predicate{ boolean test(T t); }
一言以蔽之,函数式接口就是只定义一个抽象方法的接口。
注意:接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方式,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。
@FunctionalInterface如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface的标注。这个标注用于表示该接口会设计成一个函数式接口。如果你用@FunctionalInterface定义一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误消息可能是“Multiple non-overriding abstract methods found in interface Foo”,表面存在多个抽象方法。请注意,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override标注表示方法被重写了。
把Lambda付诸实现:环绕执行模式让我们通过一个例子,看看在实践中如何利用Lambda和行为参数化来让代码更为灵活,更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式。
public static String processFile() throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return br.readLine(); } }第1步:记得行为参数化
现在这段diam是有局限的。你只能读取文件的第一行。如果你想要返回头两行,甚至是返回使用最频繁的词,该怎么办呢?在理想的情况下,你要重用执行设置和清理的代码,并告诉processFile方法对文件执行不同的 *** 作。这听起来是不是很耳熟?是的,你需要把processFile的行为参数化。你需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。
传递行为正是Lambda的拿手好戏。那要是想一次读两行,这个新的processFile方法看起来又该是什么样呢?基本上,你需要一个接收BufferedReader并返回String的Lambda。例如,下面就是从BufferedReader中打印两行的写法:
String result = processFile((BufferedReader brd) -> br.readLine() + br.readLine());第2步:使用函数式接口来传递行为
我们前面解释过了,Lambda仅可用于上下文是函数式接口的情况。你需要创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。让我们把这一接口叫做BufferedReaderProcessor吧。
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
现在你就可以把这个接口作为新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor p) throws IOException { ... }第3步:执行一个行为
任何BufferedReader -> String形式的Lambda都可以作为参数来传递,因为他们符合BufferedReaderProcessor接口中定义的process方法的签名。现在你只需要一种方法在processFile主体内执行Lambda所代表的代码。请记住,Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表示作为函数式接口的一个实例。因此,你可以在processFile主体内,对得到的BufferedReaderProcessor对象调用process方法执行处理。
public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } }第4步:传递Lambda
现在你就可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件了。
处理一行:
String oneLine = processFile((BufferedReader br) -> br.readLine());
处理两行:
String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());
我们已经展示了如何利用函数式接口来传递Lambda,但你还是得定义你自己的接口。在下一节中,我们会探讨Java8中加入的新接口,你可以重用它来传递多个不同的Lambda。
使用函数式接口 Predicatejava.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。这恰恰和你先前创建的一样,现在就可以直接使用了。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String对象的Lambda表达式,如下所示:
@FunctionalInterface public interface PredicateConsumer Function{ boolean test(T t); } public static List filter(List list, Predicate p) { List results = new ArrayList<>(); for(T s : list) { results.add(s); } return results; } Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List nonEmpty = filter(listOfString, nonEmptyStringPredicate);
Java API中提供的最常用的函数式接口及其函数描述符。请记得这只是一个起点。如果有需要,你可以自己设计一个。请记住,
(T,U)-> R 的表达式展示了应当如何思考一个函数描述符。这里它代表一个函数,具有两个参数,分别为泛型T和U,返回类型为R。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)