Java 函數語言程式設計 - 快速指南



函數語言程式設計 - 概述

在函數語言程式設計正規化中,應用程式主要使用純函式編寫。這裡的純函式是指沒有副作用的函式。副作用的一個例子是在函式返回值的同時修改例項級變數。

以下是函數語言程式設計的關鍵方面。

  • 函式 - 函式是一段執行特定任務的語句塊。函式接受資料,處理它並返回結果。編寫函式的主要目的是支援可重用性的概念。一旦編寫了函式,就可以輕鬆地呼叫它,而無需一遍又一遍地編寫相同的程式碼。

    函數語言程式設計圍繞一等函式、純函式和高階函式展開。

    • 一等函式是可以使用諸如字串、數字等一等實體作為引數、可以作為返回值或分配給變數的函式。

    • 高階函式是可以將函式作為引數或返回函式的函式。

    • 純函式是在執行期間沒有副作用的函式。

  • 函式組合 - 在指令式程式設計中,函式用於組織可執行程式碼,重點在於程式碼的組織。但在函數語言程式設計中,重點在於函式如何組織和組合。通常,資料和函式作為引數一起傳遞並返回。這使得程式設計更加強大和富有表現力。

  • 流暢介面 - 流暢介面有助於構建易於編寫和理解的表示式。這些介面有助於在每個方法返回型別再次重用時連結方法呼叫。例如 -

LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
  • 急切求值與惰性求值 - 急切求值意味著表示式在遇到時立即求值,而惰性求值則指的是延遲執行直到滿足某個條件。例如,Java 8 中的流方法在遇到終止操作時求值。

  • 持久化資料結構

    - 持久化資料結構維護其先前版本。每當資料結構狀態發生變化時,都會建立一個新的結構副本,因此資料結構保持有效不變。這種不可變集合是執行緒安全的。
  • 遞迴 - 可以透過建立迴圈或更優雅地使用遞迴來完成重複計算。如果一個函式呼叫自身,則稱為遞迴函式。

  • 並行 - 沒有副作用的函式可以按任何順序呼叫,因此是惰性求值的候選者。Java 中的函數語言程式設計使用流支援並行,其中提供了並行處理。

  • Optional - Optional 是一個特殊的類,它強制函式永遠不能返回 null。它應該使用 Optional 類物件返回值。此返回的物件具有方法 isPresent,可以檢查它以僅在存在時獲取值。

Java 函數語言程式設計 - 函式

函式是一段執行特定任務的語句塊。函式接受資料,處理它並返回結果。編寫函式的主要目的是支援可重用性的概念。一旦編寫了函式,就可以輕鬆地呼叫它,而無需一遍又一遍地編寫相同的程式碼。

函數語言程式設計圍繞一等函式、純函式和高階函式展開。

  • 一等函式是可以使用諸如字串、數字等一等實體作為引數、可以作為返回值或分配給變數的函式。

  • 高階函式是可以將函式作為引數或返回函式的函式。

  • 純函式是在執行期間沒有副作用的函式。

一等函式

一等函式可以像變數一樣對待。這意味著它可以作為引數傳遞給函式,可以由函式返回,也可以分配給變數。Java 使用 lambda 表示式支援一等函式。lambda 表示式類似於匿名函式。請參見下面的示例 -

public class FunctionTester {
   public static void main(String[] args) {
      int[] array = {1, 2, 3, 4, 5};
      SquareMaker squareMaker = item -> item * item;
      for(int i = 0; i < array.length; i++){
         System.out.println(squareMaker.square(array[i]));
      }
   }
}
interface SquareMaker {
   int square(int item);
}

輸出

1
4
9
16
25

這裡我們使用 lambda 表示式建立了 square 函式的實現,並將其分配給變數 squareMaker。

高階函式

高階函式要麼將函式作為引數,要麼返回函式。在 Java 中,我們可以傳遞或返回 lambda 表示式來實現此功能。

import java.util.function.Function;

public class FunctionTester {
   public static void main(String[] args) {
      int[] array = {1, 2, 3, 4, 5};

      Function<Integer, Integer> square = t -> t * t;        
      Function<Integer, Integer> cube = t -> t * t * t;

      for(int i = 0; i < array.length; i++){
         print(square, array[i]);
      }        
      for(int i = 0; i < array.length; i++){
         print(cube, array[i]);
      }
   }

   private static <T, R> void print(Function<T, R> function, T t ) {
      System.out.println(function.apply(t));
   }
}

輸出

1
4
9
16
25
1
8
27
64
125

純函式

純函式不會修改任何全域性變數或修改作為引數傳遞給它的任何引用。因此它沒有副作用。當使用相同的引數呼叫時,它始終返回相同的值。此類函式非常有用且執行緒安全。在下面的示例中,sum 是一個純函式。

public class FunctionTester {
   public static void main(String[] args) {
      int a, b;
      a = 1;
      b = 2;
      System.out.println(sum(a, b));
   }

   private static int sum(int a, int b){
      return a + b;
   }
}

輸出

3

Java 函數語言程式設計 - 組合

函式組合指的是將多個函式組合成單個函式的技術。我們可以將 lambda 表示式組合在一起。Java 使用 Predicate 和 Function 類提供內建支援。以下示例顯示瞭如何使用謂詞方法組合兩個函式。

import java.util.function.Predicate;
public class FunctionTester {
   public static void main(String[] args) {
      Predicate<String> hasName = text -> text.contains("name");
      Predicate<String> hasPassword = text -> text.contains("password");
      Predicate<String> hasBothNameAndPassword = hasName.and(hasPassword);
      String queryString = "name=test;password=test";
      System.out.println(hasBothNameAndPassword.test(queryString));
   }
}

輸出

true

Predicate 提供 and() 和 or() 方法來組合函式。而 Function 提供 compose 和 andThen 方法來組合函式。以下示例顯示瞭如何使用 Function 方法組合兩個函式。

import java.util.function.Function;
public class FunctionTester {
   public static void main(String[] args) {
      Function<Integer, Integer> multiply = t -> t *3;
      Function<Integer, Integer> add = t -> t  + 3;
      Function<Integer, Integer> FirstMultiplyThenAdd = multiply.compose(add);
      Function<Integer, Integer> FirstAddThenMultiply = multiply.andThen(add);
      System.out.println(FirstMultiplyThenAdd.apply(3));
      System.out.println(FirstAddThenMultiply.apply(3));
   }
}

輸出

18
12

急切求值與惰性求值

急切求值意味著表示式在遇到時立即求值,而惰性求值則指的是在需要時求值表示式。請參閱以下示例以瞭解該概念。

import java.util.function.Supplier;

public class FunctionTester {
   public static void main(String[] args) {
      String queryString = "password=test";
      System.out.println(checkInEagerWay(hasName(queryString)
         , hasPassword(queryString)));
      System.out.println(checkInLazyWay(() -> hasName(queryString)
         , () -> hasPassword(queryString)));
   }

   private static boolean hasName(String queryString){
      System.out.println("Checking name: ");
      return queryString.contains("name");
   }

   private static boolean hasPassword(String queryString){
      System.out.println("Checking password: ");
      return queryString.contains("password");
   } 

   private static String checkInEagerWay(boolean result1, boolean result2){
      return (result1 && result2) ? "all conditions passed": "failed.";
   }

   private static String checkInLazyWay(Supplier<Boolean> result1, Supplier<Boolean> result2){
      return (result1.get() && result2.get()) ? "all conditions passed": "failed.";
   }
}

輸出

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

這裡 checkInEagerWay() 函式首先求值引數,然後執行其語句。而 checkInLazyWay() 執行其語句並在需要時求值引數。由於 && 是一個短路運算子,CheckInLazyWay 僅求值第一個引數,該引數為 false,並且根本不求值第二個引數。

持久化資料結構

如果資料結構能夠將其先前的更新作為單獨的版本維護,並且可以訪問和更新每個版本,則稱該資料結構為持久化。這使得資料結構不可變且執行緒安全。例如,Java 中的 String 類物件是不可變的。每當我們對字串進行任何更改時,JVM 都會建立另一個字串物件,將新值分配給它並保留舊值作為舊字串物件。

持久化資料結構也稱為函式式資料結構。考慮以下情況 -

非持久化方式

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

持久化方式

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

Java 函數語言程式設計 - 遞迴

遞迴是在滿足某些條件之前在一個函式中呼叫同一個函式。它有助於將大問題分解成小問題。遞迴還可以使程式碼更具可讀性和表現力。

命令式與遞迴

以下示例展示了使用這兩種技術計算自然數之和。

public class FunctionTester {
   public static void main(String[] args) {
      System.out.println("Sum using imperative way. Sum(5) : " + sum(5));
      System.out.println("Sum using recursive way. Sum(5) : " + sumRecursive(5));
   }

   private static int sum(int n){
      int result = 0;
      for(int i = 1; i <= n; i++){
         result = result + i;
      }
      return result;
   }

   private static int sumRecursive(int n){
      if(n == 1){
         return 1;
      }else{
         return n + sumRecursive(n-1);
      }
   }
}

輸出

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

使用遞迴,我們將 n-1 個自然數之和的結果與 n 相加以獲得所需的結果。

尾遞迴

尾遞迴表示遞迴方法呼叫應該在最後。以下示例展示了使用尾遞迴列印數字序列。

public class FunctionTester {
   public static void main(String[] args) {
      printUsingTailRecursion(5);
   }

   public static void printUsingTailRecursion(int n){
      if(n == 0) 
         return;
      else
         System.out.println(n);
      printUsingTailRecursion(n-1);
   }
}

輸出

5
4
3
2
1

頭遞迴

頭遞迴表示遞迴方法呼叫應該在程式碼的開頭。以下示例展示了使用頭遞迴列印數字序列。

public class FunctionTester {
   public static void main(String[] args) {     
      printUsingHeadRecursion(5);
   }

   public static void printUsingHeadRecursion(int n){
      if(n == 0) 
         return;
      else
         printUsingHeadRecursion(n-1); 
      System.out.println(n);
   }
}

輸出

1
2
3
4
5

Java 函數語言程式設計 - 並行

並行是函數語言程式設計的一個關鍵概念,其中一個大任務透過將其分解成較小的獨立任務來完成,然後這些小任務以並行方式完成,然後組合起來以提供完整的結果。隨著多核處理器的出現,此技術有助於加快程式碼執行速度。Java 有基於執行緒的程式設計支援用於並行處理,但它相當難以學習並且難以在沒有錯誤的情況下實現。從 Java 8 開始,流具有 parallel 方法,集合具有 parallelStream() 方法以並行方式完成任務。請參見下面的示例

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FunctionTester {
   public static void main(String[] args) {

      Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 };
      List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray));

      System.out.println("List using Serial Stream:");
      listOfIntegers
         .stream()
         .forEach(e -> System.out.print(e + " "));
      System.out.println("");

      System.out.println("List using Parallel Stream:");
      listOfIntegers
         .parallelStream()
         .forEach(e -> System.out.print(e + " "));
      System.out.println("");

      System.out.println("List using Another Parallel Stream:");
      listOfIntegers
         .stream()
         .parallel()
         .forEach(e -> System.out.print(e + " "));
      System.out.println("");

      System.out.println("List using Parallel Stream but Ordered:");
      listOfIntegers
         .parallelStream()
         .forEachOrdered(e -> System.out.print(e + " "));
         System.out.println(""); 
   } 
}

輸出

List using Serial Stream:
1 2 3 4 5 6 7 8 
List using Parallel Stream:
6 5 8 7 3 4 2 1 
List using Another Parallel Stream:
6 2 1 7 4 3 8 5 
List using Parallel Stream but Ordered:
1 2 3 4 5 6 7 8 

Optional 和 Monad

Monad 是函數語言程式設計的一個關鍵概念。Monad 是一種設計模式,有助於表示缺失的值。它允許包裝潛在的 null 值,允許在其周圍放置轉換,並在存在時提取實際值。根據定義,monad 是一組以下引數。

  • 引數化型別 - M<T>

  • 單元函式 - T -> M<T>

  • 繫結操作 - M<T> 繫結 T -> M<U> = M<U>

關鍵操作

  • 左恆等式 - 如果一個函式繫結在一個特定值的 monad 上,那麼它的結果將與將函式應用於該值相同。

  • 右恆等式 - 如果一個 monad 返回方法與原始值的 monad 相同。

  • 結合律 - 函式可以按任何順序應用於 monad。

Optional 類

Java 8 引入了 Optional 類,它是一個 monad。它提供與 monad 等效的操作。例如,return 是一個接受值並返回 monad 的操作。Optional.of() 接受引數並返回 Optional 物件。在類似的基礎上,bind 是一個將函式繫結到 monad 以生成 monad 的操作。Optional.flatMap() 是一個對 Optional 執行操作並將結果作為 Optional 返回的方法。

  • 引數化型別 - Optional<T>

  • 單元函式 - Optional.of()

  • 繫結操作 - Optional.flatMap()

示例 - 左恆等式

以下示例顯示了 Optional 類如何遵循左恆等式規則。

import java.util.Optional;
import java.util.function.Function;

public class FunctionTester {
   public static void main(String[] args) {
      Function<Integer, Optional<Integer>> addOneToX 
         = x −> Optional.of(x + 1);
      System.out.println(Optional.of(5).flatMap(addOneToX)
         .equals(addOneToX.apply(5)));
   } 
}

輸出

true

示例 - 右恆等式

以下示例顯示了 Optional 類如何遵循右恆等式規則。

import java.util.Optional;

public class FunctionTester {
   public static void main(String[] args) {
      System.out.println(Optional.of(5).flatMap(Optional::of)
         .equals(Optional.of(5)));
   } 
}

輸出

true

示例 - 結合律

以下示例顯示了 Optional 類如何遵循結合律規則。

import java.util.Optional;
import java.util.function.Function;

public class FunctionTester {
   public static void main(String[] args) {
      Function<Integer, Optional<Integer>> addOneToX 
         = x −> Optional.of(x + 1);
      Function<Integer, Optional<Integer>> addTwoToX 
         = x −> Optional.of(x + 2);
      Function<Integer, Optional<Integer>> addThreeToX 
         = x −> addOneToX.apply(x).flatMap(addTwoToX);
      Optional.of(5).flatMap(addOneToX).flatMap(addTwoToX)
         .equals(Optional.of(5).flatMap(addThreeToX));
   } 
}

輸出

true

Java 函數語言程式設計 - 閉包

閉包(Closure)是一個函式,它結合了函式及其周圍的狀態。閉包函式通常可以訪問外部函式的作用域。在下面給出的示例中,我們建立了一個函式 getWeekDay(String[] days),它返回一個可以返回工作日文字等價物的函式。這裡 getWeekDay() 是一個閉包,它返回一個圍繞呼叫函式作用域的函式。

以下示例展示了閉包是如何工作的。

import java.util.function.Function;

public class FunctionTester {
   public static void main(String[] args) {
      String[] weekDays = {"Monday", "Tuesday", "Wednesday", "Thursday",
         "Friday", "Saturday", "Sunday" };
      Function<Integer, String> getIndianWeekDay = getWeekDay(weekDays);
      System.out.println(getIndianWeekDay.apply(6));      
   }

   public static Function<Integer, String> getWeekDay(String[] weekDays){
      return index -> index >= 0 ? weekDays[index % 7] : null;
   }
}

輸出

Sunday

Java 函數語言程式設計 - 柯里化

柯里化(Currying)是一種技術,它用多個帶有較少引數的方法呼叫替換多引數函式呼叫。

請參見下面的等式。

(1 + 2 + 3) = 1 + (2 + 3) = 1 + 5 = 6

用函式表示

f(1,2,3) = g(1) + h(2 + 3) = 1 + 5 = 6

這種函式的級聯稱為柯里化,對級聯函式的呼叫必須與呼叫主函式得到相同的結果。

以下示例展示了柯里化是如何工作的。

import java.util.function.Function;

public class FunctionTester {
   public static void main(String[] args) {
      Function<Integer, Function<Integer, Function<Integer, Integer>>> 
         addNumbers = u -> v -> w -> u + v + w;             
      int result = addNumbers.apply(2).apply(3).apply(4);        
      System.out.println(result);
   } 
}

輸出

9

Java 函數語言程式設計 - 歸約

在函數語言程式設計中,歸約(Reducing)是一種將一系列值歸約為單個結果的技術,方法是對所有值應用一個函式。從 Java 8 開始,Java 在 Stream 類中提供了 reduce() 函式。流具有內建的歸約方法,如 sum()、average()、count() 等,它們作用於流中的所有元素並返回單個結果。

以下示例展示了歸約是如何工作的。

import java.util.stream.IntStream;

public class FunctionTester {
   public static void main(String[] args) {

      //1 * 2 * 3 * 4 = 24
      int product = IntStream.range(1, 5) 
         .reduce((num1, num2) -> num1 * num2)
         .orElse(-1); 

      //1 + 2 + 3 + 4 = 10
      int sum =  IntStream.range(1, 5).sum();

      System.out.println(product);
      System.out.println(sum);
   } 
}

輸出

24
10

函數語言程式設計 - Lambda 表示式

Lambda 表示式是在 Java 8 中引入的,被譽為 Java 8 最大的特性。Lambda 表示式促進了函數語言程式設計,並極大地簡化了開發。

語法

Lambda 表示式的語法特徵如下。

parameter -> expression body

以下是 Lambda 表示式的重要特徵。

  • 可選型別宣告 - 無需宣告引數的型別。編譯器可以從引數的值推斷出型別。

  • 引數周圍可選的圓括號 - 無需在圓括號中宣告單個引數。對於多個引數,需要使用圓括號。

  • 可選的大括號 - 如果表示式體包含單個語句,則無需使用大括號。

  • 可選的 return 關鍵字 - 如果表示式體只有一個表示式來返回值,編譯器會自動返回值。需要使用大括號來指示表示式返回值。

Lambda 表示式示例

使用您選擇的任何編輯器建立以下 Java 程式,例如,在 C:\> JAVA 中。

Java8Tester.java

public class Java8Tester {

   public static void main(String args[]) {
      Java8Tester tester = new Java8Tester();
		
      //with type declaration
      MathOperation addition = (int a, int b) -> a + b;
		
      //with out type declaration
      MathOperation subtraction = (a, b) -> a - b;
		
      //with return statement along with curly braces
      MathOperation multiplication = (int a, int b) -> { return a * b; };
		
      //without return statement and without curly braces
      MathOperation division = (int a, int b) -> a / b;
		
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
		
      //without parenthesis
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
		
      //with parenthesis
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
		
      greetService1.sayMessage("Mahesh");
      greetService2.sayMessage("Suresh");
   }
	
   interface MathOperation {
      int operation(int a, int b);
   }
	
   interface GreetingService {
      void sayMessage(String message);
   }
	
   private int operate(int a, int b, MathOperation mathOperation) {
      return mathOperation.operation(a, b);
   }
}

驗證結果

使用javac編譯器編譯類,如下所示 -

C:\JAVA>javac Java8Tester.java

現在執行 Java8Tester,如下所示 -

C:\JAVA>java Java8Tester

它應該產生以下輸出 -

10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Mahesh
Hello Suresh

以下是在上述示例中需要考慮的重要事項。

  • Lambda 表示式主要用於定義函式式介面的內聯實現,即只有一個方法的介面。在上面的示例中,我們使用了各種型別的 Lambda 表示式來定義 MathOperation 介面的操作方法。然後我們定義了 GreetingService 的 sayMessage 的實現。

  • Lambda 表示式消除了匿名類的需要,併為 Java 提供了一種非常簡單但功能強大的函數語言程式設計能力。

作用域

使用 Lambda 表示式,您可以引用任何 final 變數或有效 final 變數(僅賦值一次)。如果變數第二次被賦值,Lambda 表示式會丟擲編譯錯誤。

作用域示例

使用您選擇的任何編輯器建立以下 Java 程式,例如,在 C:\> JAVA 中。

Java8Tester.java

public class Java8Tester {

   final static String salutation = "Hello! ";
   
   public static void main(String args[]) {
      GreetingService greetService1 = message -> 
      System.out.println(salutation + message);
      greetService1.sayMessage("Mahesh");
   }
	
   interface GreetingService {
      void sayMessage(String message);
   }
}

驗證結果

使用javac編譯器編譯類,如下所示 -

C:\JAVA>javac Java8Tester.java

現在執行 Java8Tester,如下所示 -

C:\JAVA>java Java8Tester

它應該產生以下輸出 -

Hello! Mahesh

函數語言程式設計 - 預設方法

Java 8 引入了介面中預設方法實現的新概念。新增此功能是為了向後相容,以便舊介面可以利用 Java 8 的 Lambda 表示式功能。

例如,'List' 或 'Collection' 介面沒有 'forEach' 方法宣告。因此,新增此方法只會破壞集合框架的實現。Java 8 引入了預設方法,以便 List/Collection 介面可以具有 forEach 方法的預設實現,並且實現這些介面的類無需實現相同的程式碼。

語法

public interface vehicle {

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

多個預設方法

在介面中使用預設函式時,存在一個類實現兩個具有相同預設方法的介面的可能性。以下程式碼說明了如何解決這種歧義。

public interface vehicle {

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

public interface fourWheeler {

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

第一個解決方案是建立一個覆蓋預設實現的自己的方法。

public class car implements vehicle, fourWheeler {

   public void print() {
      System.out.println("I am a four wheeler car vehicle!");
   }
}

第二個解決方案是使用 super 呼叫指定介面的預設方法。

public class car implements vehicle, fourWheeler {

   default void print() {
      vehicle.super.print();
   }
}

靜態預設方法

從 Java 8 開始,介面也可以擁有靜態輔助方法。

public interface vehicle {

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

預設方法示例

使用您選擇的任何編輯器建立以下 Java 程式,例如,在 C:\> JAVA 中。

Java8Tester.java

public class Java8Tester {

   public static void main(String args[]) {
      Vehicle vehicle = new Car();
      vehicle.print();
   }
}

interface Vehicle {

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

interface FourWheeler {

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

class Car implements Vehicle, FourWheeler {

   public void print() {
      Vehicle.super.print();
      FourWheeler.super.print();
      Vehicle.blowHorn();
      System.out.println("I am a car!");
   }
}

驗證結果

使用javac編譯器編譯類,如下所示 -

C:\JAVA>javac Java8Tester.java

現在執行 Java8Tester,如下所示 -

C:\JAVA>java Java8Tester

它應該產生以下輸出 -

I am a vehicle!
I am a four wheeler!
Blowing horn!!!
I am a car!

函數語言程式設計 - 函式式介面

函式式介面只有一個功能可以體現。例如,具有單個方法 'compareTo' 的 Comparable 介面用於比較目的。Java 8 定義了許多函式式介面,以便在 Lambda 表示式中廣泛使用。以下是 java.util.Function 包中定義的函式式介面列表。

序號 介面及描述
1

BiConsumer<T,U>

表示一個操作,它接受兩個輸入引數,並且不返回任何結果。

2

BiFunction<T,U,R>

表示一個函式,它接受兩個引數併產生一個結果。

3

BinaryOperator<T>

表示對相同型別的兩個運算元進行操作,產生與運算元型別相同的結果。

4

BiPredicate<T,U>

表示一個雙引數的謂詞(布林值函式)。

5

BooleanSupplier

表示布林值結果的供應商。

6

Consumer<T>

表示一個操作,它接受一個單一的輸入引數並且不返回任何結果。

7

DoubleBinaryOperator

表示對兩個雙精度值運算元進行操作併產生雙精度值結果的操作。

8

DoubleConsumer

表示一個操作,它接受一個單一的雙精度值引數並且不返回任何結果。

9

DoubleFunction<R>

表示一個函式,它接受一個雙精度值引數併產生一個結果。

10

DoublePredicate

表示一個單一雙精度值引數的謂詞(布林值函式)。

11

DoubleSupplier

表示雙精度值結果的供應商。

12

DoubleToIntFunction

表示一個函式,它接受一個雙精度值引數併產生一個整數值結果。

13

DoubleToLongFunction

表示一個函式,它接受一個雙精度值引數併產生一個長整數值結果。

14

DoubleUnaryOperator

表示對單個雙精度值運算元進行操作併產生雙精度值結果的操作。

15

Function<T,R>

表示一個函式,它接受一個引數併產生一個結果。

16

IntBinaryOperator

表示對兩個整數值運算元進行操作併產生整數值結果的操作。

17

IntConsumer

表示一個操作,它接受一個單一的整數值引數並且不返回任何結果。

18

IntFunction<R>

表示一個函式,它接受一個整數值引數併產生一個結果。

19

IntPredicate

表示一個單一整數值引數的謂詞(布林值函式)。

20

IntSupplier

表示整數值結果的供應商。

21

IntToDoubleFunction

表示一個函式,它接受一個整數值引數併產生一個雙精度值結果。

22

IntToLongFunction

表示一個函式,它接受一個整數值引數併產生一個長整數值結果。

23

IntUnaryOperator

表示對單個整數值運算元進行操作併產生整數值結果的操作。

24

LongBinaryOperator

表示對兩個長整數值運算元進行操作併產生長整數值結果的操作。

25

LongConsumer

表示一個操作,它接受一個單一長整數值引數並且不返回任何結果。

26

LongFunction<R>

表示一個函式,它接受一個長整數值引數併產生一個結果。

27

LongPredicate

表示一個單一長整數值引數的謂詞(布林值函式)。

28

LongSupplier

表示長整數值結果的供應商。

29

LongToDoubleFunction

表示一個函式,它接受一個長整數值引數併產生一個雙精度值結果。

30

LongToIntFunction

表示一個函式,它接受一個長整數值引數併產生一個整數值結果。

31

LongUnaryOperator

表示對單個長整數值運算元進行操作併產生長整數值結果的操作。

32

ObjDoubleConsumer<T>

表示一個操作,它接受一個物件值和一個雙精度值引數,並且不返回任何結果。

33

ObjIntConsumer<T>

表示一個操作,它接受一個物件值和一個整數值引數,並且不返回任何結果。

34

ObjLongConsumer<T>

表示一個操作,它接受一個物件值和一個長整數值引數,並且不返回任何結果。

35

Predicate<T>

表示一個單引數的謂詞(布林值函式)。

36

Supplier<T>

表示結果的供應商。

37

ToDoubleBiFunction<T,U>

表示一個函式,它接受兩個引數併產生一個雙精度值結果。

38

ToDoubleFunction<T>

表示一個函式,它產生一個雙精度值結果。

39

ToIntBiFunction<T,U>

表示一個函式,它接受兩個引數併產生一個整數值結果。

40

ToIntFunction<T>

表示一個函式,它產生一個整數值結果。

41

ToLongBiFunction<T,U>

表示一個函式,它接受兩個引數併產生一個長整數值結果。

42

ToLongFunction<T>

表示一個函式,它產生一個長整數值結果。

43

UnaryOperator<T>

表示對單個運算元進行操作併產生與其運算元型別相同的結果的操作。

函式式介面示例

Predicate <T> 介面是一個函式式介面,具有一個方法 test(Object) 來返回一個布林值。此介面表示對物件進行測試以確定其為真或假。

使用您選擇的任何編輯器建立以下 Java 程式,例如,在 C:\> JAVA 中。

Java8Tester.java

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Java8Tester {
   public static void main(String args[]) {
      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
		
      // Predicate<Integer> predicate = n -> true
      // n is passed as parameter to test method of Predicate interface
      // test method will always return true no matter what value n has.
		
      System.out.println("Print all numbers:");
		
      //pass n as parameter
      eval(list, n->true);
		
      // Predicate<Integer> predicate1 = n -> n%2 == 0
      // n is passed as parameter to test method of Predicate interface
      // test method will return true if n%2 comes to be zero
		
      System.out.println("Print even numbers:");
      eval(list, n-> n%2 == 0 );
		
      // Predicate<Integer> predicate2 = n -> n > 3
      // n is passed as parameter to test method of Predicate interface
      // test method will return true if n is greater than 3.
		
      System.out.println("Print numbers greater than 3:");
      eval(list, n-> n > 3 );
   }
	
   public static void eval(List<Integer> list, Predicate<Integer> predicate) {
      for(Integer n: list) {
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

這裡我們傳遞了 Predicate 介面,它接受一個輸入並返回布林值。

驗證結果

使用javac編譯器編譯類,如下所示 -

C:\JAVA>javac Java8Tester.java

現在執行 Java8Tester,如下所示 -

C:\JAVA>java Java8Tester

它應該產生以下輸出 -

Print all numbers:
1
2
3
4
5
6
7
8
9
Print even numbers:
2
4
6
8
Print numbers greater than 3:
4
5
6
7
8
9

函數語言程式設計 - 方法引用

方法引用有助於透過名稱指向方法。方法引用使用 "::" 符號描述。方法引用可以用於指向以下型別的方法 -

  • 靜態方法 - 靜態方法可以使用 ClassName::Method name 表示法引用。

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

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

以下示例展示了從 Java 8 開始方法引用是如何工作的。

interface Factory {
   Vehicle prepare(String make, String model, int year);
}

class Vehicle {
   private String make;
   private String model;
   private int year;

   Vehicle(String make, String model, int year){
      this.make = make;
      this.model = model;
      this.year = year;
   }

   public String toString(){
      return "Vehicle[" + make +", " + model + ", " + year+ "]";
   }    
}

class VehicleFactory {
   static Vehicle prepareVehicleInStaticMode(String make, String model, int year){
      return new Vehicle(make, model, year);
   }

   Vehicle prepareVehicle(String make, String model, int year){
      return new Vehicle(make, model, year);
   }
}

public class FunctionTester {    
   public static void main(String[] args) {               
      //Method Reference - Static way
      Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;        
      Vehicle carHyundai = vehicle_factory_static.prepare("Hyundai", "Verna", 2018);
      System.out.println(carHyundai);

      //Method Reference - Instance way
      Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle;        
      Vehicle carTata = vehicle_factory_instance.prepare("Tata", "Harrier", 2019);
      System.out.println(carTata); 
   } 
}

輸出

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

構造器引用

建構函式引用有助於指向建構函式方法。建構函式引用使用 "::new" 符號訪問。

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

以下示例展示了從 Java 8 開始建構函式引用是如何工作的。

interface Factory {
   Vehicle prepare(String make, String model, int year);
}

class Vehicle {
   private String make;
   private String model;
   private int year;

   Vehicle(String make, String model, int year){
      this.make = make;
      this.model = model;
      this.year = year;
   }

   public String toString(){
      return "Vehicle[" + make +", " + model + ", " + year+ "]";
   }    
}

public class FunctionTester {
   static Vehicle factory(Factory factoryObj, String make, String model, int year){
      return factoryObj.prepare(make, model, year);
   }

   public static void main(String[] args) {       
      //Constructor reference
      Factory vehicle_factory = Vehicle::new;
      Vehicle carHonda = factory(vehicle_factory, "Honda", "Civic", 2017);
      System.out.println(carHonda);
   } 
}

輸出

Vehicle[Honda, Civic, 2017]

Java 函數語言程式設計 - 集合

從 Java 8 開始,流被引入到 Java 中,並且向集合添加了方法以獲取流。一旦從集合中檢索到流物件,我們就可以對集合應用各種函數語言程式設計方面,例如過濾、對映、歸約等。請參見下面的示例 -

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FunctionTester {    
   public static void main(String[] args) {               
      List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

      //Mapping
      //get list of unique squares
      List<Integer> squaresList = numbers.stream().map( i -> i*i)
         .distinct().collect(Collectors.toList());
      System.out.println(squaresList);

      //Filering 
      //get list of non-empty strings
      List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      List<String> nonEmptyStrings = strings.stream()
         .filter(string -> !string.isEmpty()).collect(Collectors.toList());
      System.out.println(nonEmptyStrings);

      //Reducing
      int sum = numbers.stream().reduce((num1, num2) -> num1 + num2).orElse(-1);
      System.out.println(sum);
   } 
}

輸出

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

函數語言程式設計 - 高階函式

如果函式滿足以下任何一個條件,則該函式被視為高階函式。

  • 它將一個或多個引數作為函式。

  • 它在執行後返回一個函式。

Java 8 Collections.sort() 方法是高階函式的理想示例。它接受一個比較方法作為引數。請參見下面的示例 -

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class FunctionTester {    
   public static void main(String[] args) {               
      List<Integer> numbers = Arrays.asList(3, 4, 6, 7, 9);

      //Passing a function as lambda expression
      Collections.sort(numbers, (a,b) ->{ return a.compareTo(b); });

      System.out.println(numbers);
      Comparator<Integer> comparator = (a,b) ->{ return a.compareTo(b); };
      Comparator<Integer> reverseComparator = comparator.reversed();
      
      //Passing a function
      Collections.sort(numbers, reverseComparator);
      System.out.println(numbers);
   } 
}

輸出

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

函數語言程式設計 - 返回函式

高階函式可以返回一個函式,但是如何在 Java 8 中實現呢?Java 8 提供了 Function 介面,它可以接受 lambda 表示式。高階函式可以返回一個 lambda 表示式,因此這個高階函式可以用來建立任意數量的函式。請看下面的例子:

import java.util.function.Function;

public class FunctionTester {    
   public static void main(String[] args) {               
      Function<Integer, Integer> addOne = adder(1);
      Function<Integer, Integer> addTwo = adder(2);
      Function<Integer, Integer> addThree = adder(3);

      //result = 4 + 1 = 5
      Integer result = addOne.apply(4);
      System.out.println(result);

      //result = 4 + 2 = 6
      result = addTwo.apply(4);
      System.out.println(result);

      //result = 4 + 3 = 7
      result = addThree.apply(4);
      System.out.println(result);
   }

   //adder - High Order Function
   //returns a function as lambda expression
   static Function<Integer, Integer> adder(Integer x){
      return y -> y + x;
   }
}

輸出

5
6
7

函數語言程式設計 - 一等函式

如果一個函式滿足以下條件,則稱為一等函式。

  • 它可以作為引數傳遞給函式。

  • 它可以從函式中返回。

  • 它可以被賦值給一個變數,然後可以在以後使用。

Java 8 使用 lambda 表示式支援函式作為一等物件。lambda 表示式是函式定義,可以賦值給變數,可以作為引數傳遞,也可以返回。請看下面的例子:

@FunctionalInterface
interface Calculator<X, Y> {    
   public X compute(X a, Y b);
}

public class FunctionTester {    
   public static void main(String[] args) {               
      //Assign a function to a variable
      Calculator<Integer, Integer> calculator = (a,b) -> a * b;

      //call a function using function variable
      System.out.println(calculator.compute(2, 3));

      //Pass the function as a parameter
      printResult(calculator, 2, 3);

      //Get the function as a return result
      Calculator<Integer, Integer> calculator1 = getCalculator();
      System.out.println(calculator1.compute(2, 3));
   }

   //Function as a parameter
   static void printResult(Calculator<Integer, Integer> calculator, Integer a, Integer b){
      System.out.println(calculator.compute(a, b));
   }

   //Function as return value
   static Calculator<Integer, Integer> getCalculator(){
      Calculator<Integer, Integer> calculator = (a,b) -> a * b;
      return calculator;
   }
}

輸出

6
6
6

函數語言程式設計 - 純函式

如果一個函式滿足以下兩個條件,則被認為是純函式:

  • 對於給定的輸入,它始終返回相同的結果,並且其結果完全取決於傳遞的輸入。

  • 它沒有副作用,意味著它不會修改呼叫方實體的任何狀態。

示例 - 純函式

public class FunctionTester {    
   public static void main(String[] args) {
      int result = sum(2,3);
      System.out.println(result);
  
      result = sum(2,3);
      System.out.println(result);
   }
   static int sum(int a, int b){
      return a + b;
   }
}

輸出

5
5

這裡sum()是一個純函式,因為它在不同時間傳遞 2 和 3 作為引數時始終返回 5,並且沒有副作用。

示例 - 非純函式

public class FunctionTester {
   private static double valueUsed = 0.0; 
   public static void main(String[] args) {
      double result = randomSum(2.0,3.0);
      System.out.println(result);
      result = randomSum(2.0,3.0);
      System.out.println(result);
   }
   
   static double randomSum(double a, double b){
      valueUsed = Math.random();       
      return valueUsed + a + b;
   }
}

輸出

5.919716721877799
5.4830887819586795

這裡randomSum()是一個非純函式,因為它在不同時間傳遞 2 和 3 作為引數時返回不同的結果,並且修改了例項變數的狀態。

函數語言程式設計 - 型別推斷

型別推斷是一種技術,編譯器透過它自動推斷傳遞的引數的型別或方法的返回型別。從 Java 8 開始,Lambda 表示式主要使用型別推斷。

請看下面的例子,瞭解型別推斷。

示例 - 型別推斷

public class FunctionTester {

   public static void main(String[] args) {
      Join<Integer,Integer,Integer> sum = (a,b) ->  a + b;
      System.out.println(sum.compute(10,20));

      Join<String, String, String> concat = (a,b) ->  a + b;
      System.out.println(concat.compute("Hello ","World!"));
   }

   interface Join<K,V,R>{
      R compute(K k ,V v);
   }
}

輸出

30
Hello World!

Lambda 表示式最初將每個引數及其返回型別都視為 Object,然後根據情況推斷資料型別。在第一個例子中,推斷的型別是 Integer,在第二個例子中推斷的型別是 String。

Lambda 表示式中的異常處理

當函式丟擲檢查異常時,Lambda 表示式很難編寫。請看下面的例子:

import java.net.URLEncoder;
import java.util.Arrays;
import java.util.stream.Collectors;

public class FunctionTester {
   public static void main(String[] args) {
      String url = "www.google.com";
      System.out.println(encodedAddress(url));
   }   

   public static String encodedAddress(String... address) {
      return Arrays.stream(address)
         .map(s -> URLEncoder.encode(s, "UTF-8"))
         .collect(Collectors.joining(","));
   }
}

以上程式碼無法編譯,因為 URLEncode.encode() 丟擲 UnsupportedEncodingException,而 encodeAddress() 方法無法丟擲該異常。

一種可能的解決方案是將 URLEncoder.encode() 提取到一個單獨的方法中,並在那裡處理異常。

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.stream.Collectors;

public class FunctionTester {
   public static void main(String[] args) {
      String url = "www.google.com";       
      System.out.println(encodedAddress(url));
   }   

   public static String encodedString(String s) {
      try {
         URLEncoder.encode(s, "UTF-8");
      }
      catch (UnsupportedEncodingException e) {        
         e.printStackTrace();
      }
      return s;
   }

   public static String encodedAddress(String... address) {
      return Arrays.stream(address)
         .map(s -> encodedString(s))
         .collect(Collectors.joining(","));
   }   
}

但是當我們有多個丟擲異常的此類方法時,以上方法不是很好。請參閱以下使用函式式介面和包裝方法的通用解決方案。

import java.net.URLEncoder;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FunctionTester {
   public static void main(String[] args) {
      String url = "www.google.com";       
      System.out.println(encodedAddress(url));
   }   
   public static String encodedAddress(String... address) {
      return Arrays.stream(address)
         .map(wrapper(s -> URLEncoder.encode(s, "UTF-8")))
         .collect(Collectors.joining(","));
   }

   private static <T, R, E extends Exception> Function<T, R> 
   wrapper(FunctionWithThrows<T, R, E> fe) {
      return arg -> {
         try {
            return fe.apply(arg);
         } catch (Exception e) {
            throw new RuntimeException(e);
         }
      };
   }
}

@FunctionalInterface
interface FunctionWithThrows<T, R, E extends Exception> {
   R apply(T t) throws E;
}

輸出

www.google.com

中間操作

Stream API 在 Java 8 中引入,以促進 Java 中的函數語言程式設計。Stream API 旨在以函式式的方式處理物件集合。根據定義,Stream 是一個 Java 元件,它可以對其元素進行內部迭代。

Stream 介面具有終止方法和非終止方法。非終止方法是指向流新增監聽器的操作。當呼叫流的終止方法時,流元素的內部迭代開始,併為每個元素呼叫附加到流的監聽器,結果由終止方法收集。

這些非終止方法稱為中間方法。中間方法只能透過呼叫終止方法來呼叫。以下是 Stream 介面的一些重要的中間方法。

  • filter - 根據給定條件過濾掉流中不需要的元素。此方法接受一個謂詞並將其應用於每個元素。如果謂詞函式返回 true,則元素包含在返回的流中。

  • map - 根據給定條件將流的每個元素對映到另一個專案。此方法接受一個函式並將其應用於每個元素。例如,將流中的每個 String 元素轉換為大寫 String 元素。

  • flatMap - 此方法可用於根據給定條件將流的每個元素對映到多個專案。當需要將複雜物件分解為簡單物件時,使用此方法。例如,將句子列表轉換為單詞列表。

  • distinct - 如果存在重複項,則返回唯一元素的流。

  • limit - 返回一個有限元素的流,其中限制透過將數字傳遞給 limit 方法來指定。

示例 - 中間方法

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class FunctionTester {    
   public static void main(String[] args) {
      List<String> stringList = 
         Arrays.asList("One", "Two", "Three", "Four", "Five", "One");       

      System.out.println("Example - Filter\n");
      //Filter strings whose length are greater than 3.
      Stream<String> longStrings = stringList
         .stream()
         .filter( s -> {return s.length() > 3; });

      //print strings
      longStrings.forEach(System.out::println);

      System.out.println("\nExample - Map\n");
      //map strings to UPPER case and print
      stringList
         .stream()
         .map( s -> s.toUpperCase())
         .forEach(System.out::println);

      List<String> sentenceList 
         = Arrays.asList("I am Mahesh.", "I love Java 8 Streams.");     

      System.out.println("\nExample - flatMap\n");
      //map strings to UPPER case and print
      sentenceList
         .stream()
         .flatMap( s -> { return  (Stream<String>) 
            Arrays.asList(s.split(" ")).stream(); })
         .forEach(System.out::println);     

      System.out.println("\nExample - distinct\n");
      //map strings to UPPER case and print
      stringList
         .stream()
         .distinct()
         .forEach(System.out::println);     

      System.out.println("\nExample - limit\n");
      //map strings to UPPER case and print
      stringList
         .stream()
         .limit(2)
         .forEach(System.out::println);        
   }   
}

輸出

Example - Filter

Three
Four
Five

Example - Map

ONE
TWO
THREE
FOUR
FIVE
ONE

Example - flatMap

I
am
Mahesh.
I
love
Java
8
Streams.

Example - distinct

One
Two
Three
Four
Five

Example - limit

One
Two

函數語言程式設計 - 終止方法

當在流上呼叫終止方法時,迭代開始於流和任何其他連結的流。迭代完成後,返回終止方法的結果。終止方法不返回 Stream,因此一旦在流上呼叫終止方法,其非終止方法或中間方法的連結就會停止/終止。

通常,終止方法返回單個值,並在流的每個元素上呼叫。以下是 Stream 介面的一些重要的終止方法。每個終止函式都採用一個謂詞函式,初始化元素的迭代,對每個元素應用謂詞。

  • anyMatch - 如果謂詞對任何元素返回 true,則返回 true。如果沒有元素匹配,則返回 false。

  • allMatch - 如果謂詞對任何元素返回 false,則返回 false。如果所有元素都匹配,則返回 true。

  • noneMatch - 如果沒有元素匹配,則返回 true,否則返回 false。

  • collect - 將每個元素儲存到傳遞的集合中。

  • count - 返回透過中間方法傳遞的元素的計數。

  • findAny - 返回包含任何元素的 Optional 例項,或者返回空例項。

  • findFirst - 返回 Optional 例項中的第一個元素。對於空流,返回空例項。

  • forEach - 對每個元素應用消費者函式。用於列印流的所有元素。

  • min - 返回流中最小的元素。根據傳遞的比較器謂詞比較元素。

  • max - 返回流中最大的元素。根據傳遞的比較器謂詞比較元素。

  • reduce - 使用傳遞的謂詞將所有元素減少為單個元素。

  • toArray - 返回流元素的陣列。

示例 - 終止方法

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FunctionTester {    
   public static void main(String[] args) {
      List<String> stringList 
         = Arrays.asList("One", "Two", "Three", "Four", "Five", "One");       

      System.out.println("Example - anyMatch\n");
      //anyMatch - check if Two is present?
      System.out.println("Two is present: " 
         + stringList
         .stream()
         .anyMatch(s -> {return s.contains("Two");}));

      System.out.println("\nExample - allMatch\n");
      //allMatch - check if length of each string is greater than 2.
      System.out.println("Length > 2: " 
         + stringList
         .stream()
         .allMatch(s -> {return s.length() > 2;}));

      System.out.println("\nExample - noneMatch\n");
      //noneMatch - check if length of each string is greater than 6.
      System.out.println("Length > 6: " 
         + stringList
         .stream()
         .noneMatch(s -> {return s.length() > 6;}));

      System.out.println("\nExample - collect\n");
      System.out.println("List: " 
         + stringList
         .stream()
         .filter(s -> {return s.length() > 3;})
         .collect(Collectors.toList()));

      System.out.println("\nExample - count\n");
      System.out.println("Count: " 
         + stringList
         .stream()
         .filter(s -> {return s.length() > 3;})
         .count());

      System.out.println("\nExample - findAny\n");
      System.out.println("findAny: " 
         + stringList
         .stream()      
         .findAny().get());

      System.out.println("\nExample - findFirst\n");
      System.out.println("findFirst: " 
         + stringList
         .stream()      
         .findFirst().get());

      System.out.println("\nExample - forEach\n");
      stringList
         .stream()      
         .forEach(System.out::println);

      System.out.println("\nExample - min\n");
      System.out.println("min: " 
         + stringList
         .stream()      
         .min((s1, s2) -> { return s1.compareTo(s2);}));

      System.out.println("\nExample - max\n");
      System.out.println("min: " 
         + stringList
         .stream()      
         .max((s1, s2) -> { return s1.compareTo(s2);}));

      System.out.println("\nExample - reduce\n");
      System.out.println("reduced: " 
         + stringList
         .stream()      
         .reduce((s1, s2) -> { return s1 + ", "+ s2;})
         .get());
   }   
}

輸出

Example - anyMatch

Two is present: true

Example - allMatch

Length > 2: true

Example - noneMatch

Length > 6: true

Example - collect

List: [Three, Four, Five]

Example - count

Count: 3

Example - findAny

findAny: One

Example - findFirst

findFirst: One

Example - forEach

One
Two
Three
Four
Five
One

Example - min

min: Optional[Five]

Example - max

min: Optional[Two]

Example - reduce

reduced: One, Two, Three, Four, Five, One

函數語言程式設計 - 無限流

集合是在記憶體中的資料結構,其中包含集合中的所有元素,並且我們有外部迭代來遍歷集合,而 Stream 是一個固定的資料結構,其中元素按需計算,並且 Stream 有內建迭代來遍歷每個元素。以下示例顯示如何從陣列建立 Stream。

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

以上流的大小是固定的,由一個包含四個數字的陣列構建,並且在第四個元素之後不會返回元素。但是,我們可以使用 Stream.iterate() 或 Stream.generate() 方法建立 Stream,這些方法可以將 lambda 表示式傳遞給 Stream。使用 lambda 表示式,我們可以傳遞一個條件,一旦滿足該條件,就會給出所需的元素。考慮一下我們需要一個數字列表,這些數字是 3 的倍數的情況。

示例 - 無限流

import java.util.stream.Stream;

public class FunctionTester {    
   public static void main(String[] args) {
      //create a stream of numbers which are multiple of 3 
      Stream<Integer> numbers = Stream.iterate(0, n -> n + 3);

      numbers
         .limit(10)
         .forEach(System.out::println);
   }   
}

輸出

0
3
6
9
12
15
18
21
24
27

為了對無限流進行操作,我們使用了 Stream 介面的 limit() 方法來限制數字的迭代,當它們的計數達到 10 時。

函數語言程式設計 - 固定長度流

有多種方法可以建立固定長度的流。

  • 使用 Stream.of() 方法

  • 使用 Collection.stream() 方法

  • 使用 Stream.builder() 方法

以下示例顯示了以上所有建立固定長度流的方法。

示例 - 固定長度流

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class FunctionTester {    
   public static void main(String[] args) {

      System.out.println("Stream.of():");
      Stream<Integer> stream  = Stream.of(1, 2, 3, 4, 5);       
      stream.forEach(System.out::println);

      System.out.println("Collection.stream():");
      Integer[] numbers = {1, 2, 3, 4, 5};     
      List<Integer> list = Arrays.asList(numbers);
      list.stream().forEach(System.out::println);

      System.out.println("StreamBuilder.build():");
      Stream.Builder<Integer> streamBuilder = Stream.builder();
      streamBuilder.accept(1);
      streamBuilder.accept(2);
      streamBuilder.accept(3);
      streamBuilder.accept(4);
      streamBuilder.accept(5);
      streamBuilder.build().forEach(System.out::println);    
   }   
}

輸出

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

© . All rights reserved.