Java 函数式编程 - 快速指南
函数式编程 - 概述
在函数式编程范式中,应用程序主要使用纯函数编写。纯函数是没有副作用的函数。副作用的一个例子是修改实例级变量,同时从函数返回值。
以下是函数式编程的关键方面。
函数 − 函数是执行特定任务的语句块。函数接受数据、处理数据并返回结果。函数主要是为了支持可重用性的概念而编写的。一旦编写了函数,就可以轻松调用它,而不必一遍又一遍地编写相同的代码。
函数式编程围绕第一类函数、纯函数和高阶函数展开。
第一类函数是使用第一类实体(如字符串、数字)的函数,这些实体可以作为参数传递、返回或分配给变量。
高阶函数是可以将函数作为参数和/或返回函数的函数。
纯函数是执行时没有副作用的函数。
函数组合 − 在命令式编程中,函数用于组织可执行代码,重点是代码的组织。但在函数式编程中,重点在于函数的组织和组合方式。通常,数据和函数作为参数一起传递并返回。这使编程更强大、更具表现力。
流畅接口 − 流畅接口有助于编写易于编写和理解的表达式。当每个方法返回类型再次被重用时,这些接口有助于链接方法调用。例如 −
LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
急切求值与惰性求值 − 急切求值意味着在遇到表达式时立即求值,而惰性求值是指延迟执行直到满足某些条件。例如,当遇到终端方法时,将评估 Java 8 中的流方法。
持久数据结构
− 持久数据结构维护其先前版本。每当数据结构状态发生变化时,都会创建结构的新副本,因此数据结构实际上保持不可变。这种不可变集合是线程安全的。递归 − 可以通过循环或更优雅地使用递归来完成重复计算。如果函数调用自身,则称为递归函数。
并行性 − 没有副作用的函数可以按任何顺序调用,因此是惰性求值的候选。Java 中的函数式编程支持使用提供并行处理的流的并行性。
可选 − 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 开始,流具有并行方法,集合具有 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 是一种设计模式,有助于表示缺失值。它允许包装潜在的空值,允许在其周围进行转换并提取实际值(如果存在)。根据定义,monad 是一组以下参数。
参数化类型 − M<T>
单元函数 − T −> M<T>
绑定操作 − M<T> bind 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
示例 − 正确身份
以下示例显示了可选类如何遵循正确身份规则。
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 函数式编程 - 闭包
闭包是函数与其周围状态的组合。闭包函数通常可以访问外部函数的作用域。在下面给出的示例中,我们创建了一个函数 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 函数式编程 - 柯里化
柯里化是一种技术,其中许多参数的函数调用被具有较少参数的多个方法调用所取代。
参见以下等式。
(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
在函数式编程中,reducing 是一种通过对所有值应用函数将值流缩减为单个结果的技术。从 Java 8 开始,Java 在 Stream 类中提供了 reduce() 函数。流具有内置的 Reduce 方法,如 sum()、average()、count(),它们可作用于流的所有元素并返回单个结果。
以下示例展示了 Reducing 的工作原理。
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 表达式
Java 8 引入了 Lambda 表达式,并被誉为 Java 8 的最大特性。Lambda 表达式促进了函数式编程,并大大简化了开发。
语法
Lambda 表达式具有以下语法特征。
参数 -> 表达式主体
以下是 Lambda 表达式的重要特征。
可选类型声明 − 无需声明参数的类型。编译器可以从参数的值推断出相同的类型。
参数周围的可选括号 − 无需在括号中声明单个参数。对于多个参数,必须使用括号。
可选花括号 − 如果表达式主体包含单个语句,则无需在表达式主体中使用花括号。
可选 return 关键字 − 如果主体只有一个表达式来返回值,则编译器会自动返回该值。必须使用花括号来指示表达式返回值。
Lambda 表达式示例
使用您选择的任何编辑器(例如,C:\> JAVA)创建以下 Java 程序。
Java8Tester.java
public class Java8Tester { public static void main(String args[]) { Java8Tester tester = new Java8Tester(); //带有类型声明 MathOperation addition = (int a, int b) -> a + b; //不带有类型声明 MathOperation subtraction = (a, b) -> a - b; //带有 return 语句和花括号 MathOperation multiplication = (int a, int b) -> { return a * b; }; //不带有 return 语句和花括号 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, 加法)); System.out.println("10 - 5 = " + tester.operate(10, 5, 减法)); System.out.println("10 x 5 = " + tester.operate(10, 5, 乘法)); System.out.println("10 / 5 = " + tester.operate(10, 5, 除法)); //不带括号 GreetingServicegreetService1 = message -> System.out.println("Hello " + message); //带括号 GreetingServicegreetService2 = (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 表达式,您可以引用任何最终变量或有效最终变量(仅分配一次)。如果第二次为变量赋值,Lambda 表达式会引发编译错误。
范围示例
使用您选择的任何编辑器(例如,C:\> JAVA)创建以下 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
函数式编程 - default 默认方法
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!!!"); } }
默认方法示例
使用您选择的任何编辑器(例如,C:\> JAVA)创建以下 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 包中定义的函数式接口列表。
Sr.No. | 接口 &描述 |
---|---|
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 表示接受双值参数并产生 int 值结果的函数。 |
13 | DoubleToLongFunction 表示接受双值参数并产生长值结果的函数。 |
14 | DoubleUnaryOperator 表示对单个双值操作数执行的操作,产生双值结果。 |
15 | Function<T,R> 表示接受一个参数并产生结果的函数。 |
16 | IntBinaryOperator 表示对两个 int 值操作数进行操作并产生一个 int 值结果。 |
17 | IntConsumer 表示接受单个 int 值参数但不返回任何结果的操作。 |
18 | IntFunction<R> 表示接受 int 值参数并产生结果的函数。 |
19 | IntPredicate 表示一个 int 值参数的谓词(布尔值函数)。 |
20 | IntSupplier 表示 int 值结果的供应商。 |
21 | IntToDoubleFunction 表示接受 int 值参数并产生双值结果的函数。 |
22 | IntToLongFunction 表示接受 int 值参数并产生长值结果的函数。 |
23 | IntUnaryOperator 表示对单个 int 值操作数进行的操作,产生 int 值结果。 |
24 | LongBinaryOperator 表示对两个长值操作数进行操作并产生长值结果。 |
25 | LongConsumer 表示接受单个长值参数但不返回结果的操作。 |
26 | LongFunction<R> 表示接受长值参数并产生结果的函数。 |
27 | LongPredicate 表示一个长值参数的谓词(布尔值函数)。 |
28 | LongSupplier 表示长值结果的供应商。 |
29 | LongToDoubleFunction 表示接受长值参数并产生双值结果的函数结果。 |
30 | LongToIntFunction 表示接受长值参数并产生 int 值结果的函数。 |
31 | LongUnaryOperator 表示对单个长值操作数进行操作,产生长值结果。 |
32 | ObjDoubleConsumer<T> 表示接受对象值和双值的操作参数,并且不返回任何结果。 |
33 | ObjIntConsumer<T> 表示接受对象值和 int 值参数的操作,并且不返回任何结果。 |
34 | ObjLongConsumer<T> 表示接受对象值和长值参数的操作,并且不返回任何结果。 |
35 | 谓词<T> 表示一个参数的谓词(布尔值函数)。 |
36 | Supplier<T> 表示结果的提供者。 |
37 | ToDoubleBiFunction<T,U> 表示接受两个参数并产生双值结果的函数。 |
38 | ToDoubleFunction<T> 表示产生双值结果的函数。 |
39 | ToIntBiFunction<T,U> 表示接受两个参数并产生一个 int 值结果。 |
40 | ToIntFunction<T> 表示产生 int 值结果的函数。 |
41 | ToLongBiFunction<T,U> 表示接受两个参数并产生一个 long 值结果的函数。 |
42 | ToLongFunction<T> 表示产生长值结果。 |
43 | UnaryOperator<T> 表示对单个操作数的操作,该操作产生与其操作数相同类型的结果。 |
函数式接口示例
Predicate <T> 接口是一个函数式接口,带有方法 test(Object) 以返回布尔值。此接口表示测试对象为真或假。
使用您选择的任何编辑器(例如,C:\> JAVA)创建以下 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 作为参数传递给 Predicate 接口的测试方法 // 无论 n 的值是什么,测试方法始终返回 true。 System.out.println("打印所有数字:"); //将 n 作为参数传递 eval(list, n->true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n 作为参数传递给 Predicate 接口的测试方法 // 如果 n%2 为零,测试方法将返回 true System.out.println("打印偶数:"); eval(list, n-> n%2 == 0 ); // Predicate<Integer> predicate2 = n -> n > 3 // n 作为参数传递给 Predicate 接口的测试方法 // 如果 n 大于 3,则测试方法将返回 true。 System.out.println("打印大于 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 名称表示法引用静态方法。
//方法引用 - 静态方式 Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;
实例方法 - 可以使用 Object::Method 名称表示法引用实例方法。
//方法引用 - 实例方式 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"符号访问。
//构造函数引用 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) { //构造函数引用 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); //映射 //获取唯一正方形列表 List<Integer> squaresList = numbers.stream().map( i -> i*i) .distinct().collect(Collectors.toList()); System.out.println(squaresList); //文件归档 //获取非空字符串列表 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); //减少 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); //将函数作为 lambda 表达式传递 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(); //传递函数 Collections.sort(numbers, reverseComparator); System.out.println(numbers); } }
输出
[3, 4, 6, 7, 9] [9, 7, 6, 4, 3]
函数式编程 - 返回函数
高阶函数可以返回一个函数,但如何使用 Java 8 来实现呢?Java 8 提供了可以接受 lambda 表达式的 Function 接口。高阶函数可以返回 lamdba 表达式,因此可以使用此高阶函数创建任意数量的函数。请参见下面的示例 −
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 - 高阶函数 //以 lambda 表达式的形式返回一个函数 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) { //将函数赋值给变量 Calculator<Integer, Integer> calculator = (a,b) -> a * b; //使用函数变量调用函数 System.out.println(calculator.compute(2, 3)); //将函数作为参数传递 printResult(calculator, 2, 3); //获取函数作为返回结果 Calculator<Integer, Integer> calculator1 = getCalculator(); System.out.println(calculator1.compute(2, 3)); } //将函数作为参数 static void printResult(Calculator<Integer, Integer> calculator, Integer a, Integer b){ System.out.println(calculator.compute(a, b)); } //函数作为返回值 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
中间方法
Java 8 引入了 Stream API,以促进 Java 中的函数式编程。Stream API 旨在以函数式方式处理对象集合。根据定义,Stream 是可以对其元素进行内部迭代的 Java 组件。
Stream 接口具有终端方法和非终端方法。非终端方法是向流添加侦听器的操作。当调用流的终端方法时,将开始流元素的内部迭代,并为每个元素调用附加到流的侦听器,并通过终端方法收集结果。
此类非终端方法称为中间方法。只能通过称为终端的方法来调用中间方法。以下是 Stream 接口的一些重要中间方法。
filter − 根据给定的标准从流中过滤掉不需要的元素。此方法接受一个谓词并将其应用于每个元素。如果谓词函数返回 true,则元素将包含在返回的流中。
map − 根据给定的条件将流中的每个元素映射到另一个项目。此方法接受一个函数并将其应用于每个元素。例如,将流中的每个字符串元素转换为大写字符串元素。
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"); //过滤长度大于3的字符串。 Stream<String> longStrings = stringList .stream() .filter( s -> {return s.length() > 3; }); //打印字符串 longStrings.forEach(System.out::println); System.out.println("Example - Map"); //将字符串映射为大写并打印 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("Example - flatMap"); //将字符串映射为大写并打印 sentenceList .stream() .flatMap( s -> { return (Stream<String>) Arrays.asList(s.split(" ")).stream(); }) .forEach(System.out::println); System.out.println("Example - distinct"); //将字符串映射为大写并打印 stringList .stream() .distinct() .forEach(System.out::println); System.out.println("Example - limit"); //将字符串映射为大写并打印 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 接口的一些重要终端方法。每个终端函数都采用谓词函数,启动元素的迭代,并将谓词应用于每个元素。
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"); //anyMatch-检查是否存在 Two? System.out.println("Two is present: " + stringList .stream() .anyMatch(s -> {return s.contains("Two");})); System.out.println("Example - allMatch"); //allMatch - 检查每个字符串的长度是否大于 2。 System.out.println("Length > 2: " + stringList .stream() .allMatch(s -> {return s.length() > 2;})); System.out.println("Example - noneMatch"); //noneMatch - 检查每个字符串的长度是否大于 6。 System.out.println("Length > 6: " + stringList .stream() .noneMatch(s -> {return s.length() > 6;})); System.out.println("Example - collect"); System.out.println("List: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .collect(Collectors.toList())); System.out.println("Example - count"); System.out.println("Count: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .count()); System.out.println("Example - findAny"); System.out.println("findAny: " + stringList .stream() .findAny().get()); System.out.println("Example - findFirst"); System.out.println("findFirst: " + stringList .stream() .findFirst().get()); System.out.println("Example - forEach"); stringList .stream() .forEach(System.out::println); System.out.println("Example - min"); System.out.println("min: " + stringList .stream() .min((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("Example - max"); System.out.println("min: " + stringList .stream() .max((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("Example - reduce"); 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
函数式编程 - 无限流
集合是内存中的数据结构,其中包含集合中的所有元素,我们有外部迭代来遍历集合,而流是一种固定的数据结构,其中元素是按需计算的,并且流具有内置迭代来遍历每个元素。以下示例显示了如何从数组创建流。
int[] numbers = {1, 2, 3, 4}; IntStream numbersFromArray = Arrays.stream(numbers);
上述流的大小固定,由四个数字组成的数组构建,并且不会返回第 4 个元素之后的元素。但是我们可以使用 Stream.iterate() 或 Stream.generate() 方法创建流,该方法可以将 lamdba 表达式传递给流。使用 lamdba 表达式,我们可以传递一个条件,一旦满足该条件,就会给出所需的元素。考虑一下这种情况,我们需要一个 3 的倍数的数字列表。
示例 - 无限流
import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { //创建一个 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