Java函数式编程(五):闭包(2)
在调用filter方法之前 ,我们先调用了checkIfStartsWith()方法,把想要的字母传参进去。这个调用很快就返回了一个lambda表达式,然后我们把它传参给filter方法。
通过创建了一个高阶函数(这里是checkIfStartsWith)并且使用了词法作用域,我们成功的去除了代码中的冗余。我们不用再重复的判断name是不是以某个字母开头了。
重构,缩小作用域
在前面的例子中我们用了一个static方法,不过我们不希望用static方法来缓存变量,这样把我们的代码搞乱了。最好能把这个函数的作用域缩小到使用它的地方。我们可以用一个Function接口来实现这个。
final Function<String, Predicate<String>> startsWithLetter = (String letter) -> {
Predicate<String> checkStarts = (String name) -> name.startsWith(letter);
return checkStarts; };
这个lambda表达式取代了原来的static方法,它可以放到函数里面,在需要用到它之前定义一下就好了。startWithLetter变量引用的是一个入参是String,出参是Predicate的Function。
和使用static方法相比,这个版本简单多了,不过我们还可以对它继续重构让它更简洁点。从实际的角度看,这个函数和前面的static方法是一样的;它们都接收一个String返回一个Predicate。为了不显式的声明一个Predicate, 我们用一个lamdba表达式整个给替换掉。
final Function<String, Predicate<String>> startsWithLetter = (String letter) -> (String name) -> name.startsWith(letter);
我们把那些乱七八糟的东西给干掉了,但是我们还可以去掉类型声明,让它更简洁一点,Java编译器会根据上下文去做类型推导的。我们来看下改进后的版本。
final Function<String, Predicate<String>> startsWithLetter =
letter -> name -> name.startsWith(letter);
要适应这种简洁的语法可得下点工夫。如果它亮瞎了你的眼睛的话,先看看别的地方吧。我们已经完成了代码的重构,现在可以用它来替换掉原来的checkIfStartsWith()方法了,就像这样:
final long countFriendsStartN = friends.stream()
.filter(startsWithLetter.apply("N")).count();
final long countFriendsStartB = friends.stream()
.filter(startsWithLetter.apply("B")).count();
在这节中我们用到了高阶函数。我们看到了如果把函数传递给另一个函数,如何在函数中创建函数,以及如何通过函数来返回一个函数。这些例子都展示了lambda表达式带来的简洁性和可重用性。
本节中我们充分发挥了Function和Predicate的作用,不过我们来看下它们两个到底有什么区别。Predicate接受一个类型为T的参数,返回一个boolean值来代表它对应的判断条件的真假。当我们需要做条件判断的时候,我们可以使用Predicateg来完成。像filter这类对元素进行筛选的方法都接收Predicate作为参数。而Funciton代表的是一个函数,它的入参是类型为T的变量,返回的是R类型的一个结果。它和只能返回boolean的Predicate相比要更加通用。只要是将输入转化成一个输出的,我们都可以使用Function,因此map使用Function作为参数也是情理之中的事情了。
可以看到,从集合中选取元素非常简单。下面我们将介绍下如何从集合中只挑选出一个元素。