在函数式编程中,函数既能够接收也能够返回其余函数。函数再也不像传统的面向对象编程中同样,只是一个对象的工厂或生成器,它也可以建立和返回另外一个函数。返回函数的函数能够变成级联 lambda 表达式,特别值得注意的是代码很是简短。尽管此语法初看起来可能很是陌生,但它有本身的用途。本文将帮助您认识级联 lambda 表达式,理解它们的性质和在代码中的用途。html
您是否看到过相似这样的代码段?java
x -> y -> x > y
若是您很好奇“这究竟是什么意思?”,那么您并不孤单。对于不熟悉使用 lambda 表达式编程的开发人员,此语法可能看起来像货物正从快速行驶的卡车上一件件掉下来同样。编程
幸运的是,咱们不会常常看到它们,但理解如何建立级联 lambda 表达式和如何在代码中理解它们会大大减小您的受挫感。app
在谈论级联 lambda 表达式以前,有必要首先理解如何建立它们。对此,咱们须要回顾一下高阶函数和它们在函数分解中的做用,函数分解是一种将复杂流程分解为更小、更简单的部分的方式。ide
首先,考虑区分高阶函数与常规函数的规则:函数式编程
常规函数函数
高阶函数code
开发人员将匿名函数或 lambda 表达式传递给高阶函数,以让代码简短且富于表达。让咱们看看这些高阶函数的两个示例。htm
在 Java™ 中,咱们使用函数接口来引用 lambda 表达式和方法引用。下面这个函数接收一个对象和一个函数:对象
public static int totalSelectedValues(List<Integer> values, Predicate<Integer> selector) { return values.stream() .filter(selector) .reduce(0, Integer::sum); }
totalSelectedValues
的第一个参数是集合对象,而第二个参数是 Predicate
函数接口。 由于参数类型是函数接口 (Predicate
),因此咱们如今能够将一个 lambda 表达式做为第二个参数传递给 totalSelectedValues
。例如,若是咱们想仅对一个 numbers
列表中的偶数值求和,能够调用 totalSelectedValues
,以下所示:
totalSelectedValues(numbers, e -> e % 2 == 0);
假设咱们如今在 Util
类中有一个名为 isEven
的 static
方法。在此状况下,咱们可使用 isEven
做为 totalSelectedValues
的参数,而不传递 lambda 表达式:
totalSelectedValues(numbers, Util::isEven);
做为规则,只要一个函数接口显示为一个函数的参数的类型,您看到的就是一个高阶函数。
函数能够接收函数、lambda 表达式或方法引用做为参数。一样地,函数也能够返回 lambda 表达式或方法引用。在此状况下,返回类型将是函数接口。
让咱们首先看一个建立并返回 Predicate
来验证给定值是否为奇数的函数:
public static Predicate<Integer> createIsOdd() { Predicate<Integer> check = (Integer number) -> number % 2 != 0; return check;}
为了返回一个函数,咱们必须提供一个函数接口做为返回类型。在本例中,咱们的函数接口是 Predicate
。尽管上述代码在语法上是正确的,但它能够更加简短。 咱们使用类型引用并删除临时变量来改进该代码:
public static Predicate<Integer> createIsOdd() { return number -> number % 2 != 0;}
这是使用的 createIsOdd
方法的一个示例:
Predicate<Integer> isOdd = createIsOdd(); isOdd.test(4);
请注意,在 isOdd
上调用 test
会返回 false
。咱们也能够在 isOdd
上使用更多值来调用 test
;它并不限于使用一次。
如今您已大致了解高阶函数和如何在代码中找到它们,咱们能够考虑使用它们来让代码更加简短。
设想咱们有两个列表 numbers1
和 numbers2
。假设咱们想从第一个列表中仅提取大于 50 的数,而后从第二个列表中提取大于 50 的值并乘以 2。
可经过如下代码实现这些目的:
List<Integer> result1 = numbers1.stream() .filter(e -> e > 50) .collect(toList()); List<Integer> result2 = numbers2.stream() .filter(e -> e > 50) .map(e -> e * 2) .collect(toList());
此代码很好,但您注意到它很冗长了吗?咱们对检查数字是否大于 50 的 lambda 表达式使用了两次。 咱们能够经过建立并重用一个 Predicate
,从而删除重复代码,让代码更富于表达:
Predicate<Integer> isGreaterThan50 = number -> number > 50; List<Integer> result1 = numbers1.stream() .filter(isGreaterThan50) .collect(toList()); List<Integer> result2 = numbers2.stream() .filter(isGreaterThan50) .map(e -> e * 2) .collect(toList());
经过将 lambda 表达式存储在一个引用中,咱们能够重用它,这是咱们避免重复 lambda 表达式的方式。若是咱们想跨方法重用 lambda 表达式,也能够将该引用放入一个单独的方法中,而不是放在一个局部变量引用中。
如今假设咱们想从列表 numbers1
中提取大于 2五、50 和 75 的值。咱们能够首先编写 3 个不一样的 lambda 表达式:
List<Integer> valuesOver25 = numbers1.stream() .filter(e -> e > 25) .collect(toList()); List<Integer> valuesOver50 = numbers1.stream() .filter(e -> e > 50) .collect(toList()); List<Integer> valuesOver75 = numbers1.stream() .filter(e -> e > 75) .collect(toList());
尽管上面每一个 lambda 表达式将输入与一个不一样的值比较,但它们作的事情彻底相同。如何以较少的重复来重写此代码?
尽管上一个示例中的两个 lambda 表达式相同,但上面 3 个表达式稍微不一样。建立一个返回 Predicate
的 Function
能够解决此问题。
首先,函数接口 Function<T, U>
将一个 T
类型的输入转换为 U
类型的输出。例如,下面的示例将一个给定值转换为它的平方根:
Function<Integer, Double> sqrt = value -> Math.sqrt(value);
在这里,返回类型 U
能够很简单,好比 Double
、String
或 Person
。或者它也能够更复杂,好比 Consumer
或 Predicate
等另外一个函数接口。
在本例中,咱们但愿一个 Function
建立一个 Predicate
。因此代码以下:
Function<Integer, Predicate<Integer>> isGreaterThan = (Integer pivot) -> { Predicate<Integer> isGreaterThanPivot = (Integer candidate) -> { return candidate > pivot; }; return isGreaterThanPivot;};
引用 isGreaterThan
引用了一个表示 Function<T, U>
— 或更准确地讲表示 Function<Integer, Predicate<Integer>>
的 lambda 表达式。输入是一个 Integer
,输出是一个 Predicate<Integer>
。
在 lambda 表达式的主体中(外部 {}
内),咱们建立了另外一个引用 isGreaterThanPivot
,它包含对另外一个 lambda 表达式的引用。这一次,该引用是一个 Predicate
而不是 Function
。最后,咱们返回该引用。
isGreaterThan
是一个 lambda 表达式的引用,该表达式在调用时返回另外一个 lambda 表达式 — 换言之,这里隐藏着一种 lambda 表达式级联关系。
如今,咱们可使用新建立的外部 lamba 表达式来解决代码中的重复问题:
List<Integer> valuesOver25 = numbers1.stream() .filter(isGreaterThan.apply(25)) .collect(toList()); List<Integer> valuesOver50 = numbers1.stream() .filter(isGreaterThan.apply(50)) .collect(toList()); List<Integer> valuesOver75 = numbers1.stream() .filter(isGreaterThan.apply(75)) .collect(toList());
在 isGreaterThan
上调用 apply
会返回一个 Predicate
,后者而后做为参数传递给 filter
方法。
尽管整个过程很是简单(做为示例),可是可以抽象为一个函数对于谓词更加复杂的场景来讲尤为有用。
咱们已从代码中成功删除了重复的 lambda 表达式,但 isGreaterThan
的定义看起来仍然很杂乱。幸运的是,咱们能够组合一些 Java 8 约定来减小杂乱,让代码更简短。
咱们首先重构如下代码:
Function<Integer, Predicate<Integer>> isGreaterThan = (Integer pivot) -> { Predicate<Integer> isGreaterThanPivot = (Integer candidate) -> { return candidate > pivot; }; return isGreaterThanPivot;};
可使用类型引用来从外部和内部 lambda 表达式的参数中删除类型细节:
Function<Integer, Predicate<Integer>> isGreaterThan = (pivot) -> { Predicate<Integer> isGreaterThanPivot = (candidate) -> { return candidate > pivot; }; return isGreaterThanPivot;};
目前,咱们从代码中删除了两个单词,改进不大。
接下来,咱们删除多余的 ()
,以及外部 lambda 表达式中没必要要的临时引用:
Function<Integer, Predicate<Integer>> isGreaterThan = pivot -> { return candidate -> { return candidate > pivot; };};代码更加简短了,可是仍然看起来有些杂乱。
能够看到内部 lambda 表达式的主体只有一行,显然 {}
和 return
是多余的。让咱们删除它们:
Function<Integer, Predicate<Integer>> isGreaterThan = pivot -> { return candidate -> candidate > pivot;};
如今能够看到,外部 lambda 表达式的主体也只有一行,因此 {}
和 return
在这里也是多余的。在这里,咱们应用最后一次重构:
Function<Integer, Predicate<Integer>> isGreaterThan = pivot -> candidate -> candidate > pivot;
如今能够看到 — 这是咱们的级联 lambda 表达式。
咱们经过一个适合每一个阶段的重构过程,获得了最终的代码 - 级联 lambda 表达式。在本例中,外部 lambda 表达式接收 pivot
做为参数,内部 lambda 表达式接收 candidate
做为参数。内部 lambda 表达式的主体同时使用它收到的参数 (candidate
) 和来自外部范围的参数。也就是说,内部 lambda 表达式的主体同时依靠它的参数和它的词法范围或定义范围。
级联 lambda 表达式对于编写它的人很是有意义。可是对于读者呢?
看到一个只有一个向右箭头 (->
) 的 lambda 表达式时,您应该知道您看到的是一个匿名函数,它接受参数(多是空的)并执行一个操做或返回一个结果值。
看到一个包含两个向右箭头 (->
) 的 lambda 表达式时,您看到的也是一个匿名函数,但它接受参数(多是空的)并返回另外一个 lambda 表达式。返回的 lambda 表达式能够接受它本身的参数或者多是空的。它能够执行一个操做或返回一个值。它甚至能够返回另外一个 lambda 表达式,但这一般有点大材小用,最好避免。
大致上讲,当您看到两个向右箭头时,能够将第一个箭头右侧的全部内容视为一个黑盒:一个由外部 lambda 表达式返回的 lambda 表达式。
级联 lambda 表达式不是很常见,但您应该知道如何在代码中识别和理解它们。当一个 lambda 表达式返回另外一个 lambda 表达式,而不是接受一个操做或返回一个值时,您将看到两个箭头。这种代码很是简短,但可能在最初遇到时很是难以理解。可是,一旦您学会识别这种函数式语法,理解和掌握它就会变得容易得多。
原做者:Venkat Subramaniam
原文连接: Java 8 习惯用语
原出处: IBM Developer