Java 函数式编程 - 快速指南
函数式编程 - 概述
在函数式编程范式中,应用程序主要使用纯函数编写。这里的纯函数是指没有副作用的函数。副作用的一个例子是在函数返回值的同时修改实例级变量。
以下是函数式编程的关键方面。
函数 - 函数是一段执行特定任务的语句块。函数接受数据,处理它并返回结果。编写函数的主要目的是支持可重用性的概念。一旦编写了函数,就可以轻松地调用它,而无需一遍又一遍地编写相同的代码。
函数式编程围绕一等函数、纯函数和高阶函数展开。
一等函数是可以使用诸如字符串、数字等一等实体作为参数、可以作为返回值或分配给变量的函数。
高阶函数是可以将函数作为参数或返回函数的函数。
纯函数是在执行期间没有副作用的函数。
函数组合 - 在命令式编程中,函数用于组织可执行代码,重点在于代码的组织。但在函数式编程中,重点在于函数如何组织和组合。通常,数据和函数作为参数一起传递并返回。这使得编程更加强大和富有表现力。
流畅接口 - 流畅接口有助于构建易于编写和理解的表达式。这些接口有助于在每个方法返回类型再次重用时链接方法调用。例如 -
LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
急切求值与惰性求值 - 急切求值意味着表达式在遇到时立即求值,而惰性求值则指的是延迟执行直到满足某个条件。例如,Java 8 中的流方法在遇到终止操作时求值。
持久化数据结构
- 持久化数据结构维护其先前版本。每当数据结构状态发生变化时,都会创建一个新的结构副本,因此数据结构保持有效不变。这种不可变集合是线程安全的。递归 - 可以通过创建循环或更优雅地使用递归来完成重复计算。如果一个函数调用自身,则称为递归函数。
并行 - 没有副作用的函数可以按任何顺序调用,因此是惰性求值的候选者。Java 中的函数式编程使用流支持并行,其中提供了并行处理。
Optional - Optional 是一个特殊的类,它强制函数永远不能返回 null。它应该使用 Optional 类对象返回值。此返回的对象具有方法 isPresent,可以检查它以仅在存在时获取值。
Java 函数式编程 - 函数
函数是一段执行特定任务的语句块。函数接受数据,处理它并返回结果。编写函数的主要目的是支持可重用性的概念。一旦编写了函数,就可以轻松地调用它,而无需一遍又一遍地编写相同的代码。
函数式编程围绕一等函数、纯函数和高阶函数展开。
一等函数是可以使用诸如字符串、数字等一等实体作为参数、可以作为返回值或分配给变量的函数。
高阶函数是可以将函数作为参数或返回函数的函数。
纯函数是在执行期间没有副作用的函数。
一等函数
一等函数可以像变量一样对待。这意味着它可以作为参数传递给函数,可以由函数返回,也可以分配给变量。Java 使用 lambda 表达式支持一等函数。lambda 表达式类似于匿名函数。请参见下面的示例 -
public class FunctionTester { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; SquareMaker squareMaker = item -> item * item; for(int i = 0; i < array.length; i++){ System.out.println(squareMaker.square(array[i])); } } } interface SquareMaker { int square(int item); }
输出
1 4 9 16 25
这里我们使用 lambda 表达式创建了 square 函数的实现,并将其分配给变量 squareMaker。
高阶函数
高阶函数要么将函数作为参数,要么返回函数。在 Java 中,我们可以传递或返回 lambda 表达式来实现此功能。
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; Function<Integer, Integer> square = t -> t * t; Function<Integer, Integer> cube = t -> t * t * t; for(int i = 0; i < array.length; i++){ print(square, array[i]); } for(int i = 0; i < array.length; i++){ print(cube, array[i]); } } private static <T, R> void print(Function<T, R> function, T t ) { System.out.println(function.apply(t)); } }
输出
1 4 9 16 25 1 8 27 64 125
纯函数
纯函数不会修改任何全局变量或修改作为参数传递给它的任何引用。因此它没有副作用。当使用相同的参数调用时,它始终返回相同的值。此类函数非常有用且线程安全。在下面的示例中,sum 是一个纯函数。
public class FunctionTester { public static void main(String[] args) { int a, b; a = 1; b = 2; System.out.println(sum(a, b)); } private static int sum(int a, int b){ return a + b; } }
输出
3
Java 函数式编程 - 组合
函数组合指的是将多个函数组合成单个函数的技术。我们可以将 lambda 表达式组合在一起。Java 使用 Predicate 和 Function 类提供内置支持。以下示例显示了如何使用谓词方法组合两个函数。
import java.util.function.Predicate; public class FunctionTester { public static void main(String[] args) { Predicate<String> hasName = text -> text.contains("name"); Predicate<String> hasPassword = text -> text.contains("password"); Predicate<String> hasBothNameAndPassword = hasName.and(hasPassword); String queryString = "name=test;password=test"; System.out.println(hasBothNameAndPassword.test(queryString)); } }
输出
true
Predicate 提供 and() 和 or() 方法来组合函数。而 Function 提供 compose 和 andThen 方法来组合函数。以下示例显示了如何使用 Function 方法组合两个函数。
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Integer> multiply = t -> t *3; Function<Integer, Integer> add = t -> t + 3; Function<Integer, Integer> FirstMultiplyThenAdd = multiply.compose(add); Function<Integer, Integer> FirstAddThenMultiply = multiply.andThen(add); System.out.println(FirstMultiplyThenAdd.apply(3)); System.out.println(FirstAddThenMultiply.apply(3)); } }
输出
18 12
急切求值与惰性求值
急切求值意味着表达式在遇到时立即求值,而惰性求值则指的是在需要时求值表达式。请参阅以下示例以了解该概念。
import java.util.function.Supplier; public class FunctionTester { public static void main(String[] args) { String queryString = "password=test"; System.out.println(checkInEagerWay(hasName(queryString) , hasPassword(queryString))); System.out.println(checkInLazyWay(() -> hasName(queryString) , () -> hasPassword(queryString))); } private static boolean hasName(String queryString){ System.out.println("Checking name: "); return queryString.contains("name"); } private static boolean hasPassword(String queryString){ System.out.println("Checking password: "); return queryString.contains("password"); } private static String checkInEagerWay(boolean result1, boolean result2){ return (result1 && result2) ? "all conditions passed": "failed."; } private static String checkInLazyWay(Supplier<Boolean> result1, Supplier<Boolean> result2){ return (result1.get() && result2.get()) ? "all conditions passed": "failed."; } }
输出
Checking name: Checking password: failed. Checking name: failed.
这里 checkInEagerWay() 函数首先求值参数,然后执行其语句。而 checkInLazyWay() 执行其语句并在需要时求值参数。由于 && 是一个短路运算符,CheckInLazyWay 仅求值第一个参数,该参数为 false,并且根本不求值第二个参数。
持久化数据结构
如果数据结构能够将其先前的更新作为单独的版本维护,并且可以访问和更新每个版本,则称该数据结构为持久化。这使得数据结构不可变且线程安全。例如,Java 中的 String 类对象是不可变的。每当我们对字符串进行任何更改时,JVM 都会创建另一个字符串对象,将新值分配给它并保留旧值作为旧字符串对象。
持久化数据结构也称为函数式数据结构。考虑以下情况 -
非持久化方式
public static Person updateAge(Person person, int age){ person.setAge(age); return person; }
持久化方式
public static Person updateAge(Person pPerson, int age){ Person person = new Person(); person.setAge(age); return person; }
Java 函数式编程 - 递归
递归是在满足某些条件之前在一个函数中调用同一个函数。它有助于将大问题分解成小问题。递归还可以使代码更具可读性和表现力。
命令式与递归
以下示例展示了使用这两种技术计算自然数之和。
public class FunctionTester { public static void main(String[] args) { System.out.println("Sum using imperative way. Sum(5) : " + sum(5)); System.out.println("Sum using recursive way. Sum(5) : " + sumRecursive(5)); } private static int sum(int n){ int result = 0; for(int i = 1; i <= n; i++){ result = result + i; } return result; } private static int sumRecursive(int n){ if(n == 1){ return 1; }else{ return n + sumRecursive(n-1); } } }
输出
Sum using imperative way. Sum(5) : 15 Sum using recursive way. Sum(5) : 15
使用递归,我们将 n-1 个自然数之和的结果与 n 相加以获得所需的结果。
尾递归
尾递归表示递归方法调用应该在最后。以下示例展示了使用尾递归打印数字序列。
public class FunctionTester { public static void main(String[] args) { printUsingTailRecursion(5); } public static void printUsingTailRecursion(int n){ if(n == 0) return; else System.out.println(n); printUsingTailRecursion(n-1); } }
输出
5 4 3 2 1
头递归
头递归表示递归方法调用应该在代码的开头。以下示例展示了使用头递归打印数字序列。
public class FunctionTester { public static void main(String[] args) { printUsingHeadRecursion(5); } public static void printUsingHeadRecursion(int n){ if(n == 0) return; else printUsingHeadRecursion(n-1); System.out.println(n); } }
输出
1 2 3 4 5
Java 函数式编程 - 并行
并行是函数式编程的一个关键概念,其中一个大任务通过将其分解成较小的独立任务来完成,然后这些小任务以并行方式完成,然后组合起来以提供完整的结果。随着多核处理器的出现,此技术有助于加快代码执行速度。Java 有基于线程的编程支持用于并行处理,但它相当难以学习并且难以在没有错误的情况下实现。从 Java 8 开始,流具有 parallel 方法,集合具有 parallelStream() 方法以并行方式完成任务。请参见下面的示例
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class FunctionTester { public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 }; List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray)); System.out.println("List using Serial Stream:"); listOfIntegers .stream() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Parallel Stream:"); listOfIntegers .parallelStream() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Another Parallel Stream:"); listOfIntegers .stream() .parallel() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Parallel Stream but Ordered:"); listOfIntegers .parallelStream() .forEachOrdered(e -> System.out.print(e + " ")); System.out.println(""); } }
输出
List using Serial Stream: 1 2 3 4 5 6 7 8 List using Parallel Stream: 6 5 8 7 3 4 2 1 List using Another Parallel Stream: 6 2 1 7 4 3 8 5 List using Parallel Stream but Ordered: 1 2 3 4 5 6 7 8
Optional 和 Monad
Monad 是函数式编程的一个关键概念。Monad 是一种设计模式,有助于表示缺失的值。它允许包装潜在的 null 值,允许在其周围放置转换,并在存在时提取实际值。根据定义,monad 是一组以下参数。
参数化类型 - M<T>
单元函数 - T -> M<T>
绑定操作 - M<T> 绑定 T -> M<U> = M<U>
关键操作
左恒等式 - 如果一个函数绑定在一个特定值的 monad 上,那么它的结果将与将函数应用于该值相同。
右恒等式 - 如果一个 monad 返回方法与原始值的 monad 相同。
结合律 - 函数可以按任何顺序应用于 monad。
Optional 类
Java 8 引入了 Optional 类,它是一个 monad。它提供与 monad 等效的操作。例如,return 是一个接受值并返回 monad 的操作。Optional.of() 接受参数并返回 Optional 对象。在类似的基础上,bind 是一个将函数绑定到 monad 以生成 monad 的操作。Optional.flatMap() 是一个对 Optional 执行操作并将结果作为 Optional 返回的方法。
参数化类型 - Optional<T>
单元函数 - Optional.of()
绑定操作 - Optional.flatMap()
示例 - 左恒等式
以下示例显示了 Optional 类如何遵循左恒等式规则。
import java.util.Optional; import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Optional<Integer>> addOneToX = x −> Optional.of(x + 1); System.out.println(Optional.of(5).flatMap(addOneToX) .equals(addOneToX.apply(5))); } }
输出
true
示例 - 右恒等式
以下示例显示了 Optional 类如何遵循右恒等式规则。
import java.util.Optional; public class FunctionTester { public static void main(String[] args) { System.out.println(Optional.of(5).flatMap(Optional::of) .equals(Optional.of(5))); } }
输出
true
示例 - 结合律
以下示例显示了 Optional 类如何遵循结合律规则。
import java.util.Optional; import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Optional<Integer>> addOneToX = x −> Optional.of(x + 1); Function<Integer, Optional<Integer>> addTwoToX = x −> Optional.of(x + 2); Function<Integer, Optional<Integer>> addThreeToX = x −> addOneToX.apply(x).flatMap(addTwoToX); Optional.of(5).flatMap(addOneToX).flatMap(addTwoToX) .equals(Optional.of(5).flatMap(addThreeToX)); } }
输出
true
Java 函数式编程 - 闭包
闭包(Closure)是一个函数,它结合了函数及其周围的状态。闭包函数通常可以访问外部函数的作用域。在下面给出的示例中,我们创建了一个函数 getWeekDay(String[] days),它返回一个可以返回工作日文本等价物的函数。这里 getWeekDay() 是一个闭包,它返回一个围绕调用函数作用域的函数。
以下示例展示了闭包是如何工作的。
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { String[] weekDays = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; Function<Integer, String> getIndianWeekDay = getWeekDay(weekDays); System.out.println(getIndianWeekDay.apply(6)); } public static Function<Integer, String> getWeekDay(String[] weekDays){ return index -> index >= 0 ? weekDays[index % 7] : null; } }
输出
Sunday
Java 函数式编程 - 柯里化
柯里化(Currying)是一种技术,它用多个带有较少参数的方法调用替换多参数函数调用。
请参见下面的等式。
(1 + 2 + 3) = 1 + (2 + 3) = 1 + 5 = 6
用函数表示
f(1,2,3) = g(1) + h(2 + 3) = 1 + 5 = 6
这种函数的级联称为柯里化,对级联函数的调用必须与调用主函数得到相同的结果。
以下示例展示了柯里化是如何工作的。
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Function<Integer, Function<Integer, Integer>>> addNumbers = u -> v -> w -> u + v + w; int result = addNumbers.apply(2).apply(3).apply(4); System.out.println(result); } }
输出
9
Java 函数式编程 - 归约
在函数式编程中,归约(Reducing)是一种将一系列值归约为单个结果的技术,方法是对所有值应用一个函数。从 Java 8 开始,Java 在 Stream 类中提供了 reduce() 函数。流具有内置的归约方法,如 sum()、average()、count() 等,它们作用于流中的所有元素并返回单个结果。
以下示例展示了归约是如何工作的。
import java.util.stream.IntStream; public class FunctionTester { public static void main(String[] args) { //1 * 2 * 3 * 4 = 24 int product = IntStream.range(1, 5) .reduce((num1, num2) -> num1 * num2) .orElse(-1); //1 + 2 + 3 + 4 = 10 int sum = IntStream.range(1, 5).sum(); System.out.println(product); System.out.println(sum); } }
输出
24 10
函数式编程 - Lambda 表达式
Lambda 表达式是在 Java 8 中引入的,被誉为 Java 8 最大的特性。Lambda 表达式促进了函数式编程,并极大地简化了开发。
语法
Lambda 表达式的语法特征如下。
parameter -> expression body
以下是 Lambda 表达式的重要特征。
可选类型声明 - 无需声明参数的类型。编译器可以从参数的值推断出类型。
参数周围可选的圆括号 - 无需在圆括号中声明单个参数。对于多个参数,需要使用圆括号。
可选的大括号 - 如果表达式体包含单个语句,则无需使用大括号。
可选的 return 关键字 - 如果表达式体只有一个表达式来返回值,编译器会自动返回值。需要使用大括号来指示表达式返回值。
Lambda 表达式示例
使用您选择的任何编辑器创建以下 Java 程序,例如,在 C:\> JAVA 中。
Java8Tester.java
public class Java8Tester { public static void main(String args[]) { Java8Tester tester = new Java8Tester(); //with type declaration MathOperation addition = (int a, int b) -> a + b; //with out type declaration MathOperation subtraction = (a, b) -> a - b; //with return statement along with curly braces MathOperation multiplication = (int a, int b) -> { return a * b; }; //without return statement and without curly braces MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); //without parenthesis GreetingService greetService1 = message -> System.out.println("Hello " + message); //with parenthesis GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Mahesh"); greetService2.sayMessage("Suresh"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation) { return mathOperation.operation(a, b); } }
验证结果
使用javac编译器编译类,如下所示 -
C:\JAVA>javac Java8Tester.java
现在运行 Java8Tester,如下所示 -
C:\JAVA>java Java8Tester
它应该产生以下输出 -
10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello Mahesh Hello Suresh
以下是在上述示例中需要考虑的重要事项。
Lambda 表达式主要用于定义函数式接口的内联实现,即只有一个方法的接口。在上面的示例中,我们使用了各种类型的 Lambda 表达式来定义 MathOperation 接口的操作方法。然后我们定义了 GreetingService 的 sayMessage 的实现。
Lambda 表达式消除了匿名类的需要,并为 Java 提供了一种非常简单但功能强大的函数式编程能力。
作用域
使用 Lambda 表达式,您可以引用任何 final 变量或有效 final 变量(仅赋值一次)。如果变量第二次被赋值,Lambda 表达式会抛出编译错误。
作用域示例
使用您选择的任何编辑器创建以下 Java 程序,例如,在 C:\> JAVA 中。
Java8Tester.java
public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]) { GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Mahesh"); } interface GreetingService { void sayMessage(String message); } }
验证结果
使用javac编译器编译类,如下所示 -
C:\JAVA>javac Java8Tester.java
现在运行 Java8Tester,如下所示 -
C:\JAVA>java Java8Tester
它应该产生以下输出 -
Hello! Mahesh
函数式编程 - 默认方法
Java 8 引入了接口中默认方法实现的新概念。添加此功能是为了向后兼容,以便旧接口可以利用 Java 8 的 Lambda 表达式功能。
例如,'List' 或 'Collection' 接口没有 'forEach' 方法声明。因此,添加此方法只会破坏集合框架的实现。Java 8 引入了默认方法,以便 List/Collection 接口可以具有 forEach 方法的默认实现,并且实现这些接口的类无需实现相同的代码。
语法
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } }
多个默认方法
在接口中使用默认函数时,存在一个类实现两个具有相同默认方法的接口的可能性。以下代码说明了如何解决这种歧义。
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } } public interface fourWheeler { default void print() { System.out.println("I am a four wheeler!"); } }
第一个解决方案是创建一个覆盖默认实现的自己的方法。
public class car implements vehicle, fourWheeler { public void print() { System.out.println("I am a four wheeler car vehicle!"); } }
第二个解决方案是使用 super 调用指定接口的默认方法。
public class car implements vehicle, fourWheeler { default void print() { vehicle.super.print(); } }
静态默认方法
从 Java 8 开始,接口也可以拥有静态辅助方法。
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } static void blowHorn() { System.out.println("Blowing horn!!!"); } }
默认方法示例
使用您选择的任何编辑器创建以下 Java 程序,例如,在 C:\> JAVA 中。
Java8Tester.java
public class Java8Tester { public static void main(String args[]) { Vehicle vehicle = new Car(); vehicle.print(); } } interface Vehicle { default void print() { System.out.println("I am a vehicle!"); } static void blowHorn() { System.out.println("Blowing horn!!!"); } } interface FourWheeler { default void print() { System.out.println("I am a four wheeler!"); } } class Car implements Vehicle, FourWheeler { public void print() { Vehicle.super.print(); FourWheeler.super.print(); Vehicle.blowHorn(); System.out.println("I am a car!"); } }
验证结果
使用javac编译器编译类,如下所示 -
C:\JAVA>javac Java8Tester.java
现在运行 Java8Tester,如下所示 -
C:\JAVA>java Java8Tester
它应该产生以下输出 -
I am a vehicle! I am a four wheeler! Blowing horn!!! I am a car!
函数式编程 - 函数式接口
函数式接口只有一个功能可以体现。例如,具有单个方法 'compareTo' 的 Comparable 接口用于比较目的。Java 8 定义了许多函数式接口,以便在 Lambda 表达式中广泛使用。以下是 java.util.Function 包中定义的函数式接口列表。
序号 | 接口及描述 |
---|---|
1 | BiConsumer<T,U> 表示一个操作,它接受两个输入参数,并且不返回任何结果。 |
2 | BiFunction<T,U,R> 表示一个函数,它接受两个参数并产生一个结果。 |
3 | BinaryOperator<T> 表示对相同类型的两个操作数进行操作,产生与操作数类型相同的结果。 |
4 | BiPredicate<T,U> 表示一个双参数的谓词(布尔值函数)。 |
5 | BooleanSupplier 表示布尔值结果的供应商。 |
6 | Consumer<T> 表示一个操作,它接受一个单一的输入参数并且不返回任何结果。 |
7 | DoubleBinaryOperator 表示对两个双精度值操作数进行操作并产生双精度值结果的操作。 |
8 | DoubleConsumer 表示一个操作,它接受一个单一的双精度值参数并且不返回任何结果。 |
9 | DoubleFunction<R> 表示一个函数,它接受一个双精度值参数并产生一个结果。 |
10 | DoublePredicate 表示一个单一双精度值参数的谓词(布尔值函数)。 |
11 | DoubleSupplier 表示双精度值结果的供应商。 |
12 | DoubleToIntFunction 表示一个函数,它接受一个双精度值参数并产生一个整数值结果。 |
13 | DoubleToLongFunction 表示一个函数,它接受一个双精度值参数并产生一个长整数值结果。 |
14 | DoubleUnaryOperator 表示对单个双精度值操作数进行操作并产生双精度值结果的操作。 |
15 | Function<T,R> 表示一个函数,它接受一个参数并产生一个结果。 |
16 | IntBinaryOperator 表示对两个整数值操作数进行操作并产生整数值结果的操作。 |
17 | IntConsumer 表示一个操作,它接受一个单一的整数值参数并且不返回任何结果。 |
18 | IntFunction<R> 表示一个函数,它接受一个整数值参数并产生一个结果。 |
19 | IntPredicate 表示一个单一整数值参数的谓词(布尔值函数)。 |
20 | IntSupplier 表示整数值结果的供应商。 |
21 | IntToDoubleFunction 表示一个函数,它接受一个整数值参数并产生一个双精度值结果。 |
22 | IntToLongFunction 表示一个函数,它接受一个整数值参数并产生一个长整数值结果。 |
23 | IntUnaryOperator 表示对单个整数值操作数进行操作并产生整数值结果的操作。 |
24 | LongBinaryOperator 表示对两个长整数值操作数进行操作并产生长整数值结果的操作。 |
25 | LongConsumer 表示一个操作,它接受一个单一长整数值参数并且不返回任何结果。 |
26 | LongFunction<R> 表示一个函数,它接受一个长整数值参数并产生一个结果。 |
27 | LongPredicate 表示一个单一长整数值参数的谓词(布尔值函数)。 |
28 | LongSupplier 表示长整数值结果的供应商。 |
29 | LongToDoubleFunction 表示一个函数,它接受一个长整数值参数并产生一个双精度值结果。 |
30 | LongToIntFunction 表示一个函数,它接受一个长整数值参数并产生一个整数值结果。 |
31 | LongUnaryOperator 表示对单个长整数值操作数进行操作并产生长整数值结果的操作。 |
32 | ObjDoubleConsumer<T> 表示一个操作,它接受一个对象值和一个双精度值参数,并且不返回任何结果。 |
33 | ObjIntConsumer<T> 表示一个操作,它接受一个对象值和一个整数值参数,并且不返回任何结果。 |
34 | ObjLongConsumer<T> 表示一个操作,它接受一个对象值和一个长整数值参数,并且不返回任何结果。 |
35 | Predicate<T> 表示一个单参数的谓词(布尔值函数)。 |
36 | Supplier<T> 表示结果的供应商。 |
37 | ToDoubleBiFunction<T,U> 表示一个函数,它接受两个参数并产生一个双精度值结果。 |
38 | ToDoubleFunction<T> 表示一个函数,它产生一个双精度值结果。 |
39 | ToIntBiFunction<T,U> 表示一个函数,它接受两个参数并产生一个整数值结果。 |
40 | ToIntFunction<T> 表示一个函数,它产生一个整数值结果。 |
41 | ToLongBiFunction<T,U> 表示一个函数,它接受两个参数并产生一个长整数值结果。 |
42 | ToLongFunction<T> 表示一个函数,它产生一个长整数值结果。 |
43 | UnaryOperator<T> 表示对单个操作数进行操作并产生与其操作数类型相同的结果的操作。 |
函数式接口示例
Predicate <T> 接口是一个函数式接口,具有一个方法 test(Object) 来返回一个布尔值。此接口表示对对象进行测试以确定其为真或假。
使用您选择的任何编辑器创建以下 Java 程序,例如,在 C:\> JAVA 中。
Java8Tester.java
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; public class Java8Tester { public static void main(String args[]) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n is passed as parameter to test method of Predicate interface // test method will always return true no matter what value n has. System.out.println("Print all numbers:"); //pass n as parameter eval(list, n->true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n is passed as parameter to test method of Predicate interface // test method will return true if n%2 comes to be zero System.out.println("Print even numbers:"); eval(list, n-> n%2 == 0 ); // Predicate<Integer> predicate2 = n -> n > 3 // n is passed as parameter to test method of Predicate interface // test method will return true if n is greater than 3. System.out.println("Print numbers greater than 3:"); eval(list, n-> n > 3 ); } public static void eval(List<Integer> list, Predicate<Integer> predicate) { for(Integer n: list) { if(predicate.test(n)) { System.out.println(n + " "); } } } }
这里我们传递了 Predicate 接口,它接受一个输入并返回布尔值。
验证结果
使用javac编译器编译类,如下所示 -
C:\JAVA>javac Java8Tester.java
现在运行 Java8Tester,如下所示 -
C:\JAVA>java Java8Tester
它应该产生以下输出 -
Print all numbers: 1 2 3 4 5 6 7 8 9 Print even numbers: 2 4 6 8 Print numbers greater than 3: 4 5 6 7 8 9
函数式编程 - 方法引用
方法引用有助于通过名称指向方法。方法引用使用 "::" 符号描述。方法引用可以用于指向以下类型的方法 -
静态方法 - 静态方法可以使用 ClassName::Method name 表示法引用。
//Method Reference - Static way Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;
实例方法 - 实例方法可以使用 Object::Method name 表示法引用。
//Method Reference - Instance way Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle;
以下示例展示了从 Java 8 开始方法引用是如何工作的。
interface Factory { Vehicle prepare(String make, String model, int year); } class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year){ this.make = make; this.model = model; this.year = year; } public String toString(){ return "Vehicle[" + make +", " + model + ", " + year+ "]"; } } class VehicleFactory { static Vehicle prepareVehicleInStaticMode(String make, String model, int year){ return new Vehicle(make, model, year); } Vehicle prepareVehicle(String make, String model, int year){ return new Vehicle(make, model, year); } } public class FunctionTester { public static void main(String[] args) { //Method Reference - Static way Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode; Vehicle carHyundai = vehicle_factory_static.prepare("Hyundai", "Verna", 2018); System.out.println(carHyundai); //Method Reference - Instance way Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle; Vehicle carTata = vehicle_factory_instance.prepare("Tata", "Harrier", 2019); System.out.println(carTata); } }
输出
Vehicle[Hyundai, Verna, 2018] Vehicle[Tata, Harrier, 2019]
构造器引用
构造函数引用有助于指向构造函数方法。构造函数引用使用 "::new" 符号访问。
//Constructor reference Factory vehicle_factory = Vehicle::new;
以下示例展示了从 Java 8 开始构造函数引用是如何工作的。
interface Factory { Vehicle prepare(String make, String model, int year); } class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year){ this.make = make; this.model = model; this.year = year; } public String toString(){ return "Vehicle[" + make +", " + model + ", " + year+ "]"; } } public class FunctionTester { static Vehicle factory(Factory factoryObj, String make, String model, int year){ return factoryObj.prepare(make, model, year); } public static void main(String[] args) { //Constructor reference Factory vehicle_factory = Vehicle::new; Vehicle carHonda = factory(vehicle_factory, "Honda", "Civic", 2017); System.out.println(carHonda); } }
输出
Vehicle[Honda, Civic, 2017]
Java 函数式编程 - 集合
从 Java 8 开始,流被引入到 Java 中,并且向集合添加了方法以获取流。一旦从集合中检索到流对象,我们就可以对集合应用各种函数式编程方面,例如过滤、映射、归约等。请参见下面的示例 -
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Mapping //get list of unique squares List<Integer> squaresList = numbers.stream().map( i -> i*i) .distinct().collect(Collectors.toList()); System.out.println(squaresList); //Filering //get list of non-empty strings List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> nonEmptyStrings = strings.stream() .filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println(nonEmptyStrings); //Reducing int sum = numbers.stream().reduce((num1, num2) -> num1 + num2).orElse(-1); System.out.println(sum); } }
输出
[9, 4, 49, 25] [abc, bc, efg, abcd, jkl] 25
函数式编程 - 高阶函数
如果函数满足以下任何一个条件,则该函数被视为高阶函数。
它将一个或多个参数作为函数。
它在执行后返回一个函数。
Java 8 Collections.sort() 方法是高阶函数的理想示例。它接受一个比较方法作为参数。请参见下面的示例 -
import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(3, 4, 6, 7, 9); //Passing a function as lambda expression Collections.sort(numbers, (a,b) ->{ return a.compareTo(b); }); System.out.println(numbers); Comparator<Integer> comparator = (a,b) ->{ return a.compareTo(b); }; Comparator<Integer> reverseComparator = comparator.reversed(); //Passing a function Collections.sort(numbers, reverseComparator); System.out.println(numbers); } }
输出
[3, 4, 6, 7, 9] [9, 7, 6, 4, 3]
函数式编程 - 返回函数
高阶函数可以返回一个函数,但是如何在 Java 8 中实现呢?Java 8 提供了 Function 接口,它可以接受 lambda 表达式。高阶函数可以返回一个 lambda 表达式,因此这个高阶函数可以用来创建任意数量的函数。请看下面的例子:
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Integer> addOne = adder(1); Function<Integer, Integer> addTwo = adder(2); Function<Integer, Integer> addThree = adder(3); //result = 4 + 1 = 5 Integer result = addOne.apply(4); System.out.println(result); //result = 4 + 2 = 6 result = addTwo.apply(4); System.out.println(result); //result = 4 + 3 = 7 result = addThree.apply(4); System.out.println(result); } //adder - High Order Function //returns a function as lambda expression static Function<Integer, Integer> adder(Integer x){ return y -> y + x; } }
输出
5 6 7
函数式编程 - 一等函数
如果一个函数满足以下条件,则称为一等函数。
它可以作为参数传递给函数。
它可以从函数中返回。
它可以被赋值给一个变量,然后可以在以后使用。
Java 8 使用 lambda 表达式支持函数作为一等对象。lambda 表达式是函数定义,可以赋值给变量,可以作为参数传递,也可以返回。请看下面的例子:
@FunctionalInterface interface Calculator<X, Y> { public X compute(X a, Y b); } public class FunctionTester { public static void main(String[] args) { //Assign a function to a variable Calculator<Integer, Integer> calculator = (a,b) -> a * b; //call a function using function variable System.out.println(calculator.compute(2, 3)); //Pass the function as a parameter printResult(calculator, 2, 3); //Get the function as a return result Calculator<Integer, Integer> calculator1 = getCalculator(); System.out.println(calculator1.compute(2, 3)); } //Function as a parameter static void printResult(Calculator<Integer, Integer> calculator, Integer a, Integer b){ System.out.println(calculator.compute(a, b)); } //Function as return value static Calculator<Integer, Integer> getCalculator(){ Calculator<Integer, Integer> calculator = (a,b) -> a * b; return calculator; } }
输出
6 6 6
函数式编程 - 纯函数
如果一个函数满足以下两个条件,则被认为是纯函数:
对于给定的输入,它始终返回相同的结果,并且其结果完全取决于传递的输入。
它没有副作用,意味着它不会修改调用方实体的任何状态。
示例 - 纯函数
public class FunctionTester { public static void main(String[] args) { int result = sum(2,3); System.out.println(result); result = sum(2,3); System.out.println(result); } static int sum(int a, int b){ return a + b; } }
输出
5 5
这里sum()是一个纯函数,因为它在不同时间传递 2 和 3 作为参数时始终返回 5,并且没有副作用。
示例 - 非纯函数
public class FunctionTester { private static double valueUsed = 0.0; public static void main(String[] args) { double result = randomSum(2.0,3.0); System.out.println(result); result = randomSum(2.0,3.0); System.out.println(result); } static double randomSum(double a, double b){ valueUsed = Math.random(); return valueUsed + a + b; } }
输出
5.919716721877799 5.4830887819586795
这里randomSum()是一个非纯函数,因为它在不同时间传递 2 和 3 作为参数时返回不同的结果,并且修改了实例变量的状态。
函数式编程 - 类型推断
类型推断是一种技术,编译器通过它自动推断传递的参数的类型或方法的返回类型。从 Java 8 开始,Lambda 表达式主要使用类型推断。
请看下面的例子,了解类型推断。
示例 - 类型推断
public class FunctionTester { public static void main(String[] args) { Join<Integer,Integer,Integer> sum = (a,b) -> a + b; System.out.println(sum.compute(10,20)); Join<String, String, String> concat = (a,b) -> a + b; System.out.println(concat.compute("Hello ","World!")); } interface Join<K,V,R>{ R compute(K k ,V v); } }
输出
30 Hello World!
Lambda 表达式最初将每个参数及其返回类型都视为 Object,然后根据情况推断数据类型。在第一个例子中,推断的类型是 Integer,在第二个例子中推断的类型是 String。
Lambda 表达式中的异常处理
当函数抛出检查异常时,Lambda 表达式很难编写。请看下面的例子:
import java.net.URLEncoder; import java.util.Arrays; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(s -> URLEncoder.encode(s, "UTF-8")) .collect(Collectors.joining(",")); } }
以上代码无法编译,因为 URLEncode.encode() 抛出 UnsupportedEncodingException,而 encodeAddress() 方法无法抛出该异常。
一种可能的解决方案是将 URLEncoder.encode() 提取到一个单独的方法中,并在那里处理异常。
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedString(String s) { try { URLEncoder.encode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return s; } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(s -> encodedString(s)) .collect(Collectors.joining(",")); } }
但是当我们有多个抛出异常的此类方法时,以上方法不是很好。请参阅以下使用函数式接口和包装方法的通用解决方案。
import java.net.URLEncoder; import java.util.Arrays; import java.util.function.Function; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(wrapper(s -> URLEncoder.encode(s, "UTF-8"))) .collect(Collectors.joining(",")); } private static <T, R, E extends Exception> Function<T, R> wrapper(FunctionWithThrows<T, R, E> fe) { return arg -> { try { return fe.apply(arg); } catch (Exception e) { throw new RuntimeException(e); } }; } } @FunctionalInterface interface FunctionWithThrows<T, R, E extends Exception> { R apply(T t) throws E; }
输出
www.google.com
中间操作
Stream API 在 Java 8 中引入,以促进 Java 中的函数式编程。Stream API 旨在以函数式的方式处理对象集合。根据定义,Stream 是一个 Java 组件,它可以对其元素进行内部迭代。
Stream 接口具有终止方法和非终止方法。非终止方法是指向流添加监听器的操作。当调用流的终止方法时,流元素的内部迭代开始,并为每个元素调用附加到流的监听器,结果由终止方法收集。
这些非终止方法称为中间方法。中间方法只能通过调用终止方法来调用。以下是 Stream 接口的一些重要的中间方法。
filter - 根据给定条件过滤掉流中不需要的元素。此方法接受一个谓词并将其应用于每个元素。如果谓词函数返回 true,则元素包含在返回的流中。
map - 根据给定条件将流的每个元素映射到另一个项目。此方法接受一个函数并将其应用于每个元素。例如,将流中的每个 String 元素转换为大写 String 元素。
flatMap - 此方法可用于根据给定条件将流的每个元素映射到多个项目。当需要将复杂对象分解为简单对象时,使用此方法。例如,将句子列表转换为单词列表。
distinct - 如果存在重复项,则返回唯一元素的流。
limit - 返回一个有限元素的流,其中限制通过将数字传递给 limit 方法来指定。
示例 - 中间方法
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { List<String> stringList = Arrays.asList("One", "Two", "Three", "Four", "Five", "One"); System.out.println("Example - Filter\n"); //Filter strings whose length are greater than 3. Stream<String> longStrings = stringList .stream() .filter( s -> {return s.length() > 3; }); //print strings longStrings.forEach(System.out::println); System.out.println("\nExample - Map\n"); //map strings to UPPER case and print stringList .stream() .map( s -> s.toUpperCase()) .forEach(System.out::println); List<String> sentenceList = Arrays.asList("I am Mahesh.", "I love Java 8 Streams."); System.out.println("\nExample - flatMap\n"); //map strings to UPPER case and print sentenceList .stream() .flatMap( s -> { return (Stream<String>) Arrays.asList(s.split(" ")).stream(); }) .forEach(System.out::println); System.out.println("\nExample - distinct\n"); //map strings to UPPER case and print stringList .stream() .distinct() .forEach(System.out::println); System.out.println("\nExample - limit\n"); //map strings to UPPER case and print stringList .stream() .limit(2) .forEach(System.out::println); } }
输出
Example - Filter Three Four Five Example - Map ONE TWO THREE FOUR FIVE ONE Example - flatMap I am Mahesh. I love Java 8 Streams. Example - distinct One Two Three Four Five Example - limit One Two
函数式编程 - 终止方法
当在流上调用终止方法时,迭代开始于流和任何其他链接的流。迭代完成后,返回终止方法的结果。终止方法不返回 Stream,因此一旦在流上调用终止方法,其非终止方法或中间方法的链接就会停止/终止。
通常,终止方法返回单个值,并在流的每个元素上调用。以下是 Stream 接口的一些重要的终止方法。每个终止函数都采用一个谓词函数,初始化元素的迭代,对每个元素应用谓词。
anyMatch - 如果谓词对任何元素返回 true,则返回 true。如果没有元素匹配,则返回 false。
allMatch - 如果谓词对任何元素返回 false,则返回 false。如果所有元素都匹配,则返回 true。
noneMatch - 如果没有元素匹配,则返回 true,否则返回 false。
collect - 将每个元素存储到传递的集合中。
count - 返回通过中间方法传递的元素的计数。
findAny - 返回包含任何元素的 Optional 实例,或者返回空实例。
findFirst - 返回 Optional 实例中的第一个元素。对于空流,返回空实例。
forEach - 对每个元素应用消费者函数。用于打印流的所有元素。
min - 返回流中最小的元素。根据传递的比较器谓词比较元素。
max - 返回流中最大的元素。根据传递的比较器谓词比较元素。
reduce - 使用传递的谓词将所有元素减少为单个元素。
toArray - 返回流元素的数组。
示例 - 终止方法
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<String> stringList = Arrays.asList("One", "Two", "Three", "Four", "Five", "One"); System.out.println("Example - anyMatch\n"); //anyMatch - check if Two is present? System.out.println("Two is present: " + stringList .stream() .anyMatch(s -> {return s.contains("Two");})); System.out.println("\nExample - allMatch\n"); //allMatch - check if length of each string is greater than 2. System.out.println("Length > 2: " + stringList .stream() .allMatch(s -> {return s.length() > 2;})); System.out.println("\nExample - noneMatch\n"); //noneMatch - check if length of each string is greater than 6. System.out.println("Length > 6: " + stringList .stream() .noneMatch(s -> {return s.length() > 6;})); System.out.println("\nExample - collect\n"); System.out.println("List: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .collect(Collectors.toList())); System.out.println("\nExample - count\n"); System.out.println("Count: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .count()); System.out.println("\nExample - findAny\n"); System.out.println("findAny: " + stringList .stream() .findAny().get()); System.out.println("\nExample - findFirst\n"); System.out.println("findFirst: " + stringList .stream() .findFirst().get()); System.out.println("\nExample - forEach\n"); stringList .stream() .forEach(System.out::println); System.out.println("\nExample - min\n"); System.out.println("min: " + stringList .stream() .min((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("\nExample - max\n"); System.out.println("min: " + stringList .stream() .max((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("\nExample - reduce\n"); System.out.println("reduced: " + stringList .stream() .reduce((s1, s2) -> { return s1 + ", "+ s2;}) .get()); } }
输出
Example - anyMatch Two is present: true Example - allMatch Length > 2: true Example - noneMatch Length > 6: true Example - collect List: [Three, Four, Five] Example - count Count: 3 Example - findAny findAny: One Example - findFirst findFirst: One Example - forEach One Two Three Four Five One Example - min min: Optional[Five] Example - max min: Optional[Two] Example - reduce reduced: One, Two, Three, Four, Five, One
函数式编程 - 无限流
集合是在内存中的数据结构,其中包含集合中的所有元素,并且我们有外部迭代来遍历集合,而 Stream 是一个固定的数据结构,其中元素按需计算,并且 Stream 有内置迭代来遍历每个元素。以下示例显示如何从数组创建 Stream。
int[] numbers = {1, 2, 3, 4}; IntStream numbersFromArray = Arrays.stream(numbers);
以上流的大小是固定的,由一个包含四个数字的数组构建,并且在第四个元素之后不会返回元素。但是,我们可以使用 Stream.iterate() 或 Stream.generate() 方法创建 Stream,这些方法可以将 lambda 表达式传递给 Stream。使用 lambda 表达式,我们可以传递一个条件,一旦满足该条件,就会给出所需的元素。考虑一下我们需要一个数字列表,这些数字是 3 的倍数的情况。
示例 - 无限流
import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { //create a stream of numbers which are multiple of 3 Stream<Integer> numbers = Stream.iterate(0, n -> n + 3); numbers .limit(10) .forEach(System.out::println); } }
输出
0 3 6 9 12 15 18 21 24 27
为了对无限流进行操作,我们使用了 Stream 接口的 limit() 方法来限制数字的迭代,当它们的计数达到 10 时。
函数式编程 - 固定长度流
有多种方法可以创建固定长度的流。
使用 Stream.of() 方法
使用 Collection.stream() 方法
使用 Stream.builder() 方法
以下示例显示了以上所有创建固定长度流的方法。
示例 - 固定长度流
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { System.out.println("Stream.of():"); Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5); stream.forEach(System.out::println); System.out.println("Collection.stream():"); Integer[] numbers = {1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(numbers); list.stream().forEach(System.out::println); System.out.println("StreamBuilder.build():"); Stream.Builder<Integer> streamBuilder = Stream.builder(); streamBuilder.accept(1); streamBuilder.accept(2); streamBuilder.accept(3); streamBuilder.accept(4); streamBuilder.accept(5); streamBuilder.build().forEach(System.out::println); } }
输出
Stream.of(): 1 2 3 4 5 Collection.stream(): 1 2 3 4 5 StreamBuilder.build(): 1 2 3 4 5