Functional Programming With Java 简明教程

Functional Programming with Java - Quick Guide

Functional Programming - Overview

在函数式编程范式中,应用程序通常使用纯函数编写。此处纯函数是没有副作用的函数。副作用的一个示例是函数返回一个值的同时修改实例级别变量。

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

  1. Functions - 函数是执行特定任务的一段语句块。函数接受数据、处理它并返回一个结果。函数首先用于支持可重复使用的概念。编写函数之后,可以轻松地调用它,而无需一遍又一遍地编写相同的代码。函数式编程围绕一等函数、纯函数和高阶函数展开。一等实体 First Class Function 是字符串、数字等可以使用作参数传递、可以返回或分配给变量的实体。 High Order Function 是可以把函数用作参数和/或可以返回函数的实体。 Pure Function 是在执行过程中没有副作用的实体。

  2. Functional Composition - 在命令式编程中,函数用于组织可执行代码,而重点在于代码组织。但在函数式编程中,重点在于如何组织和组合函数。通常,数据和函数作为参数一起传递并返回。这使编程更具能力且更有表现力。

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

LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
  1. Eager vs Lazy Evaluation - 热切求值意味着一遇到表达式就对其求值,而延迟求值是指推迟执行直到满足特定条件。例如,在遇到结束方法时对 Java 8 中的流方法进行求值。

  2. 持久数据结构 - 持久数据结构维护其前一个版本。每当对数据结构状态进行更改时,都将创建一个新结构副本,使得数据结构实际上保持不变。这些不可变的集合是线程安全的。

  3. Recursion - 我们可以通过更加优雅的方式进行循环或使用递归来完成重复的计算。如果函数调用自身,则该函数称为递归函数。

  4. Parallelism - 没有副作用的函数可以按任何顺序调用,因此它们是延迟求值的候选函数。Java 中的函数式编程使用流支持并行化,其中提供了并行处理。

  5. Optionals - Optional 是一个特殊类,它强制函数不得返回空值。它应当使用 Optional 类对象返回一值。此返回对象具有 isPresent 方法,可以对其进行检查,仅当存在该值时才获取该值。

Functional Programming with Java - Functions

函数是一段执行特定任务的语句块。函数接受数据,处理数据及返回结果。函数主要编写为支持可重用性概念。一个函数一旦编写完毕,可以轻松调用,而无需一遍又一遍地编写相同的代码。

函数式编程围绕头等函数、纯函数和高阶函数展开。

  1. First Class Function 是使用字符串、数字等头等实体的函数,可以作为参数传递,可以返回,或分配给变量。

  2. High Order Function 是可以接受一个函数作为参数和/或返回一个函数的函数。

  3. Pure Function 是在执行时没有副作用的函数。

First Class Function

头等函数可以视为变量。这意味着它可以作为参数传递给函数,可以由函数返回,或者也可以分配给变量。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);
}

Output

1
4
9
16
25

在此,我们已使用 lambda 表达式创建了平方函数的实现并将其分配给变量 squareMaker。

High Order Function

高阶函数要么接受一个函数作为参数,要么返回一个函数。在 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));
   }
}

Output

1
4
9
16
25
1
8
27
64
125

Pure Function

纯函数不会修改任何全局变量或修改任何作为参数传递给它的引用。因此,它没有副作用。无论使用相同的参数调用多少次,它总能返回相同的值。此类函数非常有用,并且线程安全。在下面的示例中,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;
   }
}

Output

3

Functional Programming with Java - Composition

函数组合是指将多个函数组合到一个函数中的技术。我们可以组合 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));
   }
}

Output

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));
   }
}

Output

18
12

Eager vs Lazy Evaluation

热切求值意味着一遇到表达式就对其求值,而延迟求值是指在需要时对表达式求值。请参阅以下示例来了解该概念。

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.";
   }
}

Output

Checking name:
Checking password:
failed.
Checking name:
failed.

在 checkInEagerWay() 函数中,首先会评估参数然后执行其语句。而 checkInLazyWay() 先执行其语句,然后按需评估参数。因为 && 是一个短路运算符,所以 checkInLazyWay 只会评估为 false 的第一个参数,而根本不会评估第二个参数。

Persistent Data Structure

如果数据结构有能力将之前更新作为一个单独版本来保存,并且可以访问和更新每个版本,则称它为持续的。它使数据结构变得不可变并且是线程安全的。例如,Java 中的 String 类对象是不可变的。每当我们对字符串进行任何更改时,JVM 都会创建一个新的字符串对象,为其赋予新值并将较旧的值保留为旧的字符串对象。

持久数据结构也被称为函数式数据结构。考虑以下情况——

Non-Persistent way

public static Person updateAge(Person person, int age){
   person.setAge(age);
   return person;
}

Persistent way

public static Person updateAge(Person pPerson, int age){
   Person person = new Person();
   person.setAge(age);
   return person;
}

Functional Programming with Java - Recursion

递归是在函数中调用相同函数,直到满足特定条件。它有助于将大问题分解成小问题。递归还使代码更具可读性和表现力。

Imperative vs Recursive

以下示例展示了使用这两种技术计算自然数的总和。

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);
      }
   }
}

Output

Sum using imperative way. Sum(5) : 15
Sum using recursive way. Sum(5) : 15

使用递归,我们通过将 n-1 个自然数的总和结果与 n 相加来获取所需结果。

Tail Recursion

尾递归是指递归方法调用应位于结尾。以下示例展示了使用尾递归打印数字序列。

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);
   }
}

Output

5
4
3
2
1

Head Recursion

头递归是指递归方法调用应位于代码的开头。以下示例展示了使用头递归打印数字序列。

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);
   }
}

Output

1
2
3
4
5

Functional Programming with Java - Parallelism

并行性是函数式编程的一个关键概念,其中一个大任务完成于将分解为较小独立的任务,然后这些小任务以并行方式完成,稍后组合起来给出完整的结果。随着多核处理器的出现,此技术有助于更快地执行代码。Java 有基于线程的编程支持来进行并行处理,但它相当枯燥,并且难以在没有 bug 的情况下进行实现。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("");
   }
}

Output

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

Optionals and Monads

单子是函数式编程的一个关键概念。单子是一种设计模式,它有助于表示一个丢失的值。它允许封装一个潜在的 null 值,允许在它周围放置变换,并在存在时提取实际值。根据定义,单子是一组以下参数。

  1. A parametrized Type - M<T>

  2. A unit Function - T → M<T>

  3. A bind operation - M<T> bind T → M<U> = M<U>

Key Operations

  1. Left Identity - 如果一个函数绑定到特定值的一个单子上,那么它的结果将与将该函数应用于该值相同。

  2. Right Identity - 如果一个单子的返回方法与原始值的单子相同。

  3. Associativity - 可以在单子上以任何顺序应用函数。

Optional Class

Java 8 引入了 Optional 类,它是一个单子。它提供操作等同于单子的操作。例如, return 是一个获取值并返回单子的操作。Optional.of() 获取一个参数并返回 Optional 对象。在相同的基础上, bind 是一个将函数绑定到单子以生成单子的操作。Optional.flatMap() 是对 Optional 执行操作并将结果作为 Optional 返回的方法。

  1. A parametrized Type - Optional<T>

  2. A unit Function - Optional.of()

  3. A bind operation − Optional.flatMap()(可选)

Example − Left Identity

以下示例演示了如何让 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)));
   }
}

Output

true

Example − Right Identity

以下示例演示了如何让 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)));
   }
}

Output

true

Example - Associativity

以下示例演示了如何让 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));
   }
}

Output

true

Functional Programming with 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;
   }
}

Output

Sunday

Functional Programming with 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);
   }
}

Output

9

Functional Programming with Java - Reducing

在函数式编程中,归约是一种将值的流缩小为一个结果的技术,方法是对所有值应用一个函数。Java 从 8 开始在 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);
   }
}

Output

24
10

Functional Programming - Lambda Expressions

Java 8 中引入了 lambda 表达式,声称是 Java 8 中最大的功能。lambda 表达式促进了函数式编程,极大地简化了开发。

Syntax

Lambda 表达式的特征在于以下语法。

parameter -> expression body

以下是 Lambda 表达式的几个重要特征。

  1. Optional type declaration − 无需声明参数的类型。编译器可以从参数的值中推断出类型。

  2. Optional parenthesis around parameter - 无需在括号中声明单个参数。对于多个参数,需要使用括号。

  3. Optional curly braces - 如果主体包含单个语句,则无需在表达式主体中使用大括号。

  4. Optional return keyword - 如果主体有一个要返回值的单语句表达式,编译器会自动返回该值。需要大括号来指出表达式返回一个值。

Lambda Expressions Example

在任意编辑器中创建以下 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);
   }
}

Verify the Result

按照如下方式使用 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

以下是在上面的示例中需要考虑的几个重要事项。

  1. Lambda 表达式主要用于定义函数式接口的内联实现,即仅具有单一方法的接口。在上面的示例中,我们使用了多种类型的 lambda 表达式来定义 MathOperation 接口的 operation 方法。然后,我们定义了 GreetingService 的 sayMessage 的实现。

  2. Lambda 表达式消除了对匿名类的需求,为 Java 提供了非常简单但功能强大的函数式编程功能。

Scope

使用 lambda 表达式,你可以引用任何 final 变量或有效 final 变量(只分配一次)。如果变量第二次分配了值,lambda 表达式将抛出一个编译错误。

Scope Example

在任意编辑器中创建以下 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);
   }
}

Verify the Result

按照如下方式使用 javac 编译器编译类 −

C:\JAVA>javac Java8Tester.java

现在按照如下方式运行 Java8Tester −

C:\JAVA>java Java8Tester

它应该产生以下输出 −

Hello! Mahesh

Functional Programming - Default Methods

Java 8 在接口中引入了默认方法实现的新概念。添加此功能是为了向后兼容,以便可以使用旧接口来利用 Java 8 的 lambda 表达式功能。

例如,“List”或“Collection”接口没有“forEach”方法声明。因此,添加这种方法只会破坏集合框架实现。Java 8 引入了默认方法,以便 List/Collection 接口可以具有 forEach 方法的默认实现,而实现这些接口的类无需实现相同的接口。

Syntax

public interface vehicle {

   default void print() {
      System.out.println("I am a vehicle!");
   }
}

Multiple Defaults

使用接口中的默认函数,有可能某个类实现了具有相同默认方法的两个接口。以下代码解释了如何解决此歧义。

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();
   }
}

Static Default Methods

从 Java 8 开始,接口还可以具有静态帮助器方法。

public interface vehicle {

   default void print() {
      System.out.println("I am a vehicle!");
   }

   static void blowHorn() {
      System.out.println("Blowing horn!!!");
   }
}

Default Method Example

在任意编辑器中创建以下 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!");
   }
}

Verify the Result

按照如下方式使用 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!

Functional Programming - Functional Interfaces

函数式接口具有单一功能可表现。例如,带有单个方法“compareTo”的 Comparable 接口用于比较目的。Java 8 已经定义了很多函数式接口,以便在 lambda 表达式中广泛使用。以下是 java.util.Function 包中定义的函数式接口列表。

Functional Interface Example

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 接口,它采用一个输入并返回布尔值。

Verify the Result

按照如下方式使用 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

Functional Programming - Method References

方法引用有助于按方法的名称指向方法。使用“::”符号来描述方法引用。方法引用可用于指向以下类型的:

  1. Static methods - 可以使用类名::方法名表示法引用静态方法。

//Method Reference - Static way
Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;
  1. Instance methods - 可以使用对象::方法名表示法引用实例方法。

//Method Reference - Instance way
Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle;

以下示例展示了从 Java 8 开始,方法引用在 Java 中的工作原理。

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);
   }
}

Output

Vehicle[Hyundai, Verna, 2018]
Vehicle[Tata, Harrier, 2019]

Constructor References

构造函数引用有助于指向构造函数方法。可以使用“::new”符号访问构造函数引用。

//Constructor reference
Factory vehicle_factory = Vehicle::new;

以下示例展示了从 Java 8 开始,构造函数引用在 Java 中的工作原理。

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);
   }
}

Output

Vehicle[Honda, Civic, 2017]

Functional Programming with Java - Collections

从 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);
   }
}

Output

[9, 4, 49, 25]
[abc, bc, efg, abcd, jkl]
25

Functional Programming - High Order Functions

如果函数满足以下条件之一,则该函数被视为高阶函数。

  1. 它将一个或多个参数作为函数使用。

  2. 它在执行后返回一个函数。

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);
   }
}

Output

[3, 4, 6, 7, 9]
[9, 7, 6, 4, 3]

Functional Programming - Returning a Function

由于高阶函数可以返回函数,但如何使用 Java 8 实现。Java 8 提供了可以接受 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;
   }
}

Output

5
6
7

Functional Programming - First Class Function

如果一个函数满足以下要求,则它被称为头等函数。

  1. 它可以作为参数传递给函数。

  2. 它可以从函数返回。

  3. 它可以分配给变量,然后可以在以后使用。

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;
   }
}

Output

6
6
6

Functional Programming - Pure Function

如果函数满足以下两个条件,则该函数被视为纯函数:

  1. 对于给定的输入,它始终返回相同的结果,并且其结果完全取决于传递的输入。

  2. 它没有副作用,这意味着它不修改调用方实体的任何状态。

Example- Pure Function

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;
   }
}

Output

5
5

这里 sum() 是一个纯函数,因为它在以不同时间传递 2 和 3 作为参数时始终返回 5,并且没有副作用。

Example- Impure Function

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;
   }
}

Output

5.919716721877799
5.4830887819586795

这里 randomSum() 是一个不纯函数,因为它在以不同时间传递 2 和 3 作为参数时返回不同的结果,并修改实例变量的状态。

Functional Programming - Type Inference

类型推断是一种编译器自动推断传递的参数类型或方法返回类型的技术。从 Java 8 开始,Lambda 表达式显著地使用了类型推断。

请参阅以下示例以了解有关类型推断的说明。

Example- Type Inference

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);
   }
}

Output

30
Hello World!

Lambda 表达式最初将每个参数及其返回类型视为对象,然后据此推断其数据类型。在第一种情况下,推断出的类型为 Integer,在第二种情况下,推断出的类型为 String。

Exception Handling in Lambda Expressions

当函数抛出检查异常时,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;
}

Output

www.google.com

Intermediate Methods

Java 8 中引入了流 API 以促进 Java 中的函数式编程。流 API 针对以函数式的方式处理对象集合。根据定义,流是 Java 组件,它可以对元素执行内部迭代。

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

此类非终结方法称为中间方法。只有调用了终结方法,才能调用中间方法。以下是流接口的一些重要中间方法。

  1. filter − 根据给定条件从流中滤出不必要的元素。此方法接受谓词并在每个元素上应用它。如果谓词函数返回 true,则元素包含在返回的流中。

  2. map − 根据给定条件将流的每个元素映射至另一个项目。此方法接受函数并在每个元素上应用它。例如,将流中的每个 String 元素转换为大写 String 元素。

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

  4. distinct − 如果存在重复项,则返回唯一元素流。

  5. limit − 返回一个限制了元素的流,其中限制通过将一个数字传给 limit 方法进行限定。

Example - Intermediate Methods

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);
   }
}

Output

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

Functional Programming - Terminal Methods

当在流上调用终结方法时,对流和任何其他链接流启动迭代。迭代结束后,会返回终结方法的结果。终结方法不返回 Stream,因此一旦在流上调用终结方法,那么其对非终结方法或中间方法的链接就会停止/终止。

通常,终结方法返回一个值并针对流中的每个元素调用它们。以下是流接口的一些重要终结方法。每个终结函数都会获取一个谓词函数,启动元素的迭代,在每个元素上应用谓词。

  1. anyMatch − 如果谓词对任意元素返回 true,则返回 true。如果没有元素匹配,则返回 false。

  2. allMatch − 如果谓词对任意元素返回 false,则返回 false。如果所有元素都匹配,则返回 true。

  3. noneMatch − 如果没有元素匹配,则返回 true,否则返回 false。

  4. collect − 将每个元素存储到传递的集合中。

  5. count − 返回通过中间方法传递的元素数量。

  6. findAny − 返回包含任意元素的 Optional 实例或返回空实例。

  7. findFirst − 返回 Optional 实例中的第一个元素。对于空流,返回空实例。

  8. forEach − 在每个元素上应用使用者函数。用于打印流的所有元素。

  9. min − 返回流中最小的元素。根据通过的比较器谓词比较元素。

  10. max − 返回流中最大的元素。根据通过的比较器谓词比较元素。

  11. reduce − 使用通过的谓词将所有元素简化为单个元素。

  12. toArray − 返回流中元素的数组。

Example - Terminal Methods

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());
   }
}

Output

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

Functional Programming - Infinite Streams

集合是内存中数据结构,其中包含集合中存在的所有元素,并且我们有外部迭代来遍历集合,而流是按需计算元素的固定数据结构,并且流具有内部迭代来遍历每个元素。以下示例演示如何从数组创建流。

int[] numbers = {1, 2, 3, 4};
IntStream numbersFromArray = Arrays.stream(numbers);

上面的流具有固定大小,由四个数字数组构成,并且在第四个元素后不会返回元素。但是,我们可以使用 Stream.iterate() 或 Stream.generate() 方法创建流,该方法可以有将传递到流的 lambda 表达式。使用 lambda 表达式,我们可以传递在满足某个条件后给定所需元素的条件。考虑一种情况,我们需要一个包含 3 的倍数的数字列表。

Example - Infinite Stream

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);
   }
}

Output

0
3
6
9
12
15
18
21
24
27

为了对无限流进行操作,我们使用流接口的 limit() 方法来限制在数字计数变为 10 时的数字迭代。

Functional Programming - Fixed length Streams

我们可以使用多种方法创建固定长度流。

  1. Using Stream.of() method

  2. Using Collection.stream() method

  3. Using Stream.builder() method

以下示例演示了创建固定长度流的所有上述方法。

Example - Fix Length Stream

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);
   }
}

Output

Stream.of():
1
2
3
4
5
Collection.stream():
1
2
3
4
5
StreamBuilder.build():
1
2
3
4
5