Java 函数式编程 - 快速指南


函数式编程 - 概述

在函数式编程范式中,应用程序主要使用纯函数编写。这里的纯函数是没有副作用的函数。副作用的一个示例是在从函数返回值时修改实例级变量。

以下是函数式编程的关键方面。

  • 函数- 函数是执行特定任务的语句块。函数接受数据、处理数据并返回结果。编写函数主要是为了支持可重用性的概念。函数一旦编写完毕,就可以轻松调用,而不必一次又一次地编写相同的代码。

    函数式编程围绕一类函数、纯函数和高阶函数。

    • 第一类函数是使用第一类实体(例如字符串)的函数,数字可以作为参数传递,可以返回或分配给变量。

    • 高阶函数是一种可以将函数作为参数和/或可以返回函数的函数。

    • 纯函数是在执行时没有副作用的函数。

  • 函数组合- 在命令式编程中,函数用于组织可执行代码,重点是代码的组织。但在函数式编程中,重点是函数如何组织和组合。通常数据和函数作为参数一起传递并返回。这使得编程能力更强、更具表现力。

  • 流畅的界面- 流畅的界面有助于编写易于编写和理解的表达式。当再次重用每个方法返回类型时,这些接口有助于链接方法调用。例如 -

LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
  • 急切求值与惰性求值- 急切求值是指表达式在遇到时立即求值,而惰性求值是指延迟执行直到满足特定条件。例如,当遇到终端方法时,Java 8 中的流方法就会被评估。

  • 持久数据结构

    − 持久数据结构保留其先前版本。每当数据结构状态发生更改时,都会创建一个新的结构副本,因此数据结构实际上保持不可变。这种不可变的集合是线程安全的。
  • 递归- 可以通过制作循环或更优雅地使用递归来完成重复计算。如果一个函数调用自身,则该函数称为递归函数。

  • 并行性- 没有副作用的函数可以以任何顺序调用,因此是惰性评估的候选者。Java 中的函数式编程使用提供并行处理的流来支持并行性。

  • 可选- 可选是一个特殊的类,它强制函数永远不应该返回 null。它应该使用可选类对象返回值。返回的对象具有 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 方法来组合函数。以下示例展示了如何使用函数方法组合两个函数。

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 

选项和 Monad

Monad 是函数式编程的一个关键概念。Monad 是一种有助于表示缺失值的设计模式。它允许包装潜在的空值,允许对其进行转换并提取实际值(如果存在)。根据定义,单子是一组以下参数。

  • 参数化类型- M<T>

  • 单位函数− T −> M<T>

  • 绑定操作- M<T> 绑定 T -> M<U> = M<U>

关键操作

  • Left Identity - 如果函数绑定在特定值的 monad 上,则其结果将与函数应用于该值相同。

  • 正确的身份- 如果 monad 返回方法与原始值的 monad 相同。

  • 关联性- 函数可以以任何顺序应用在单子上。

选修课

Java 8 引入了Optional 类,它是一个monad。它提供了相当于 monad 的操作。例如return是一个接受一个值并返回 monad 的操作。Optional.of() 接受一个参数并返回可选对象。类似地,bind是将函数绑定到 monad 以生成 monad 的操作。Optional.flatMap() 是对Optional执行操作并将结果作为Optional返回的方法。

  • 参数化类型- 可选<T>

  • 一个单元函数-Optional.of()

  • 绑定操作-Optional.flatMap()

示例 - 左身份

以下示例显示了Optional类如何遵守Left Identity规则。

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类如何遵守Right Identity规则。

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 函数式编程 - 减少

在函数式编程中,缩减是一种通过对所有值应用函数来将值流缩减为单个结果的技术。从Java 8开始,Java在Stream类中提供了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 表达式具有以下语法特征。

parameter -> expression body

以下是 lambda 表达式的重要特征。

  • 可选类型声明- 无需声明参数的类型。编译器可以从参数的值推断出相同的结果。

  • 参数周围的可选括号- 无需在括号中声明单个参数。对于多个参数,需要括号。

  • 可选大括号- 如果表达式主体包含单个语句,则无需在表达式主体中使用大括号。

  • 可选 return 关键字- 如果主体具有单个表达式来返回值,则编译器会自动返回值。需要大括号来指示表达式返回值。

Lambda 表达式示例

使用您选择的任何编辑器(例如 C:\> JAVA)创建以下 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 表达式,您可以引用任何最终变量或有效最终变量(仅分配一次)。如果第二次为变量赋值,则 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

函数式编程 - 默认方法

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 包中定义的函数接口列表。

先生。 接口及说明
1

BiConsumer<T,U>

表示接受两个输入参数且不返回结果的操作。

2

双函数<T,U,R>

表示接受两个参数并产生结果的函数。

3

二元运算符<T>

表示对两个相同类型的操作数进行运算,产生与操作数相同类型的结果。

4

双谓词<T,U>

表示两个参数的谓词(布尔值函数)。

5

布尔供应商

代表布尔值结果的提供者。

6

消费者<T>

表示接受单个输入参数且不返回结果的操作。

7

双二元运算符

表示对两个双值操作数进行运算并生成双值结果。

8

双重消费者

表示接受单个双值参数且不返回结果的操作。

9

双功能<R>

表示接受双值参数并生成结果的函数。

10

双谓词

表示一个双值参数的谓词(布尔值函数)。

11

双供应商

代表双值结果的提供者。

12

DoubleToInt 函数

表示接受双值参数并生成 int 值结果的函数。

13

DoubleToLong 函数

表示接受双值参数并生成长值结果的函数。

14

双一元运算符

表示对单个双值操作数产生双值结果的运算。

15

函数<T,R>

表示一个函数,它接受一个参数并产生一个结果。

16

整数二元运算符

表示对两个 int 值操作数的运算并产生 int 值结果。

17 号

国际消费者

表示接受单个 int 值参数且不返回结果的操作。

18

整数函数<R>

表示接受 int 值参数并生成结果的函数。

19

内部谓词

表示一个 int 值参数的谓词(布尔值函数)。

20

国际供应商

代表 int 值结果的提供者。

21

整数转双精度函数

表示接受 int 值参数并生成双值结果的函数。

22

IntToLong函数

表示接受 int 值参数并生成 long 值结果的函数。

23

整型一元运算符

表示对单个 int 值操作数进行的操作,该操作会生成 int 值结果。

24

长二元运算符

表示对两个长值操作数的运算并产生长值结果。

25

长消费者

表示接受单个长值参数且不返回结果的操作。

26

长函数<R>

表示接受长值参数并生成结果的函数。

27

长谓词

表示一个长值参数的谓词(布尔值函数)。

28

长供应商

代表长期有价值的结果的提供者。

29

长到双功能

表示接受长值参数并生成双值结果的函数。

30

LongToInt函数

表示接受长值参数并生成 int 值结果的函数。

31

长一元运算符

表示对单个长值操作数进行的操作,该操作会生成长值结果。

32

ObjDoubleConsumer<T>

表示接受对象值和双值参数的操作,并且不返回结果。

33

ObjIntConsumer<T>

表示接受对象值和 int 值参数且不返回结果的操作。

34

ObjLongConsumer<T>

表示接受对象值和长值参数的操作,并且不返回结果。

35

谓词<T>

表示一个参数的谓词(布尔值函数)。

36

供应商<T>

代表结果的提供者。

37

ToDoubleBiFunction<T,U>

表示接受两个参数并生成双值结果的函数。

38

ToDoubleFunction<T>

表示产生双值结果的函数。

39

ToIntBiFunction<T,U>

表示接受两个参数并生成 int 值结果的函数。

40

ToIntFunction<T>

表示产生 int 值结果的函数。

41

ToLongBi函数<T,U>

表示接受两个参数并生成长值结果的函数。

42

ToLong函数<T>

表示产生长值结果的函数。

43

一元运算符<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 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 名称表示法进行引用。

//Method Reference - Static way
Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;        
  • 实例方法- 可以使用 Object::Method 名称表示法来引用实例方法。

//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 提供了可以接受 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 - 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

中间方法

Java 8 中引入了 Stream API,以方便 Java 中的函数式编程。Stream API 的目标是以功能方式处理对象集合。根据定义,Stream 是一个可以对其元素进行内部迭代的 Java 组件。

Stream 接口具有终端方法和非终端方法。非终端方法是向流添加侦听器的操作。当调用流的终端方法时,流元素的内部迭代开始,并且为每个元素调用附加到流的侦听器,并由终端方法收集结果。

这种非终端方法称为中间方法。中间方法只能通过调用终端方法来调用。以下是Stream接口的一些重要的中间方法。

  • filter - 根据给定标准从流中过滤掉不需要的元素。此方法接受谓词并将其应用于每个元素。如果谓词函数返回 true,则元素包含在返回的流中。

  • map - 根据给定标准将流的每个元素映射到另一个项目。此方法接受一个函数并将其应用于每个元素。例如,将流中的每个 String 元素转换为大写 String 元素。

  • flatMap - 此方法可用于根据给定条件将流的每个元素映射到多个项目。当需要将复杂对象分解为简单对象时,使用此方法。例如,将句子列表转换为单词列表。

  • unique - 如果存在重复项,则返回唯一元素流。

  • 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 - 返回包含任何元素的可选实例或返回空实例。

  • findFirst - 返回可选实例下的第一个元素。对于空流,返回空实例。

  • 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: "