到目前為止,Java 支持命令式編程風格和麵向對象編程風格。 java 添加的下一件大事是 Java 已開始通過其 Java 8 版本支持函數式編程風格。在本文中,我們將討論 Java 8 中的函數式編程。
什麽是函數式編程?
它是一種聲明式編程風格,而不是命令式編程風格。這種編程風格的基本目標是與傳統的編碼風格相比,使代碼更簡潔、更簡單、更可預測、更容易測試。函數式編程涉及某些關鍵概念,例如純函數,不可變狀態、assignment-less編程等。
函數式編程與純函數式編程:
純函數式編程語言本質上不允許任何可變性,而函數式語言提供高階函數,但通常允許可變性,但這樣做的風險是我們無法做正確的事情,這給我們帶來了負擔,而不是保護我們。所以,一般來說,我們可以說,如果一種語言提供了高階函數,那麽它就是函數式語言,如果一種語言除了高階函數之外還達到了限製可變性的程度,那麽它就變成了純粹的函數式語言。 Java 是一種函數式語言,類似於 Haskell 是一種純函數式編程語言。
我們先來了解一下其中的幾個概念函數式編程:
- 高階函數:在函數式編程中,函數被視為first-class公民。也就是說,到目前為止,在傳統的編碼風格中,我們可以使用對象執行以下操作。
- 我們可以通過對象到一個函數。
- 我們可以創造對象函數內。
- 我們可以返回對象來自一個函數。
- 我們可以通過一個函數到一個函數。
- 我們可以創建一個函數函數內。
- 我們可以返回一個函數來自一個函數。
- 純函數:如果一個函數對於相同的參數值總是返回相同的結果,並且沒有修改參數(或全局變量)或輸出某些內容等副作用,則該函數稱為純函數。
- 拉姆達表達式:Lambda 表達式是一種匿名方法,具有最低限度的可變性,並且隻有一個參數列表和一個主體。返回類型始終根據上下文推斷。另外,請注意,Lambda 表達式與函數式接口並行工作。 lambda 表達式的語法是:
(parameter) -> body
- 以其簡單的形式,lambda 可以表示為以逗號分隔的參數列表,->符號和身體。
如何用Java實現函數式編程?
Java
// Java program to demonstrate
// anonymous method
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
// Defining an anonymous method
Runnable r = new Runnable() {
public void run()
{
System.out.println(
"Running in Runnable thread");
}
};
r.run();
System.out.println(
"Running in main thread");
}
}
Running in Runnable thread Running in main thread
如果我們看一下run()方法,我們用 Runnable 包裝它。我們在 Java 7 之前都以這種方式初始化此方法。相同的程序可以在 Java 8 中重寫為:
Java
// Java 8 program to demonstrate
// a lambda expression
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
Runnable r
= ()
-> System.out.println(
"Running in Runnable thread");
r.run();
System.out.println(
"Running in main thread");
}
}
Running in Runnable thread Running in main thread
現在,上麵的代碼已經轉換成Lambda 表達式而不是匿名方法。在這裏,我們評估了一個沒有任何名稱的函數,該函數是一個 lambda 表達式。因此,在這種情況下,我們可以看到一個函數已被評估並分配給可運行的接口,並且這裏該函數已被視為first-class公民。
將一些函數從 Java 7 重構為 Java 8:
到目前為止,我們已經多次使用循環和迭代器,直到 Java 7,如下所示:
Java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator, for Each loop
for (Integer n : numbers) {
System.out.print(n + " ");
}
}
}
11 22 33 44 55 66 77 88 99 100
上麵是一個例子Java 中的 forEach 循環外部迭代器的一類,下麵又是外部迭代器的示例和另一種形式。
Java
// Java program to demonstrate an
// external iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// External iterator
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i) + " ");
}
}
}
11 22 33 44 55 66 77 88 99 100
我們可以將上麵的外部迭代器示例轉換為Java 8中引入的內部迭代器,如下所示:
Java
// Java 8 program to demonstrate
// an internal iterator
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
// Internal iterator
numbers.forEach(number
-> System.out.print(
number + " "));
}
}
11 22 33 44 55 66 77 88 99 100
在這裏,函數接口起著重要作用。無論何時需要單個抽象方法接口,我們都可以非常輕鬆地傳遞 lambda 表達式。上麵的代碼還可以進一步簡化和改進,如下:
numbers.forEach(System.out::println);
命令式編程與聲明式編程:
函數式編程風格是聲明式編程。在命令式編碼中,我們定義要執行什麽任務以及如何執行任務。然而,在聲明式編碼風格中,我們隻指定要做什麽。讓我們通過一個例子來理解這一點。給定一個數字列表,讓我們使用命令式和聲明式的編碼風格從列表中找出雙偶數的總和。
Java
// Java program to find the sum
// using imperative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int result = 0;
for (Integer n : numbers) {
if (n % 2 == 0) {
result += n * 2;
}
}
System.out.println(result);
}
}
640
上麵代碼的第一個問題是我們正在改變變量結果一次又一次。因此,可變性是命令式編碼中最大的問題之一。命令式風格的第二個問題是,我們不僅要花精力告訴要做什麽,還要花精力告訴如何進行處理。現在讓我們以聲明式的方式重寫上麵的代碼。
Java
// Java program to find the sum
// using declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * 2)
.sum());
}
}
640
從上麵的代碼來看,我們沒有改變任何變量。相反,我們將數據從一個函數轉換為另一個函數。這是命令式和聲明式之間的另一個區別。不僅如此,在上麵聲明式風格的代碼中,每個函數都是純函數,並且純函數沒有副作用。
在上麵的例子中,我們用因子 2 將數字加倍,稱為關閉。請記住,lambda 是無狀態的,而閉包具有不可變的狀態。這意味著在任何情況下,關閉都不能改變。讓我們通過一個例子來理解它。在這裏,我們將聲明一個變量因子並將在函數內使用,如下所示。
Java
// Java program to demonstrate an
// declarative style of coding
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
}
}
640
上麵的代碼運行良好,但現在讓我們嘗試在使用後對其進行修改,看看會發生什麽:
Java
import java.util.Arrays;
import java.util.List;
public class GFG {
public static void main(String[] args)
{
List<Integer> numbers
= Arrays.asList(11, 22, 33, 44,
55, 66, 77, 88,
99, 100);
int factor = 2;
System.out.println(
numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(e -> e * factor)
.sum());
factor = 3;
}
}
上麵的代碼給出了一個編譯時錯誤說在封閉範圍內定義的局部變量因子必須是最終的或有效的最終的。
該程序的時間複雜度為 O(n),其中 n 是列表中元素的數量。這是因為程序會迭代列表中的每個元素一次以應用過濾和映射操作。
空間複雜度這個程序的O(1),因為它在內存中隻存儲輸入列表和幾個整數,並且內存使用量不依賴於輸入列表的大小。
這意味著這裏的變量因子默認情況下被視為最終版本。簡而言之,我們永遠不應該嘗試改變純函數內使用的任何變量。這樣做會違反純函數規則,該規則規定純函數既不應該改變任何東西,也不應該依賴於任何改變的東西。改變任何閉包(此處為因子)都被認為是不好的閉包,因為閉包本質上總是不可變的。
相關用法
- Java Function Interface用法及代碼示例
- Java Float intBitsToFloat()用法及代碼示例
- Java Float isFinite()用法及代碼示例
- Java Float max()用法及代碼示例
- Java Float sum()用法及代碼示例
- Java Float toHexString()用法及代碼示例
- Java Float toString()用法及代碼示例
- Java Float valueOf()用法及代碼示例
- Java FileDescriptor sync()用法及代碼示例
- Java FileDescriptor valid()用法及代碼示例
- Java FileInputStream available()用法及代碼示例
- Java FileInputStream close()用法及代碼示例
- Java FileInputStream finalize()用法及代碼示例
- Java FileInputStream getChannel()用法及代碼示例
- Java FileInputStream getFD()用法及代碼示例
- Java FileInputStream skip()用法及代碼示例
- Java FileOutputStream close()用法及代碼示例
- Java FileOutputStream finalize()用法及代碼示例
- Java FileOutputStream getChannel()用法及代碼示例
- Java FileOutputStream getFD()用法及代碼示例
- Java FilePermission equals()用法及代碼示例
- Java FilePermission getActions()用法及代碼示例
- Java FilePermission hashCode()用法及代碼示例
- Java FilePermission implies()用法及代碼示例
- Java FilePermission newPermissionCollection()用法及代碼示例
注:本文由純淨天空篩選整理自asadaliasad大神的英文原創作品 Functional Programming in Java with Examples。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。