筆者對函數程式設計、匿名函數、lamda一直都不感冒。個人認為代碼看起來簡單了,但是會提高代碼的了解性難度,尤其代碼在未來需要擴充,容易造成不可擴充,因為函數程式設計本來也強調了不可變性。
不過存在即合理,并且官方也推薦,一定存在高價值性。不喜歡,可以不用,但是相關知識還是需要知曉。
函數程式設計、匿名函數和Lambda表達式概念。
- 函數程式設計(Functional Programming): 函數程式設計是一種程式設計範式,它将計算過程看作是函數之間的轉換。函數是函數程式設計的核心概念,它們可以像資料一樣傳遞、組合和操作。函數程式設計強調純函數的使用,即函數的輸出隻依賴于輸入,沒有副作用和可變狀态的影響。函數程式設計還倡導将函數作為一等公民,可以将函數指派給變量、作為參數傳遞給其他函數以及作為傳回值傳回。
- 匿名函數(Anonymous Function): 匿名函數是一種不需要命名的函數,可以直接在需要的地方定義和使用,而不必為其指定一個獨立的名稱。匿名函數通常用于需要傳遞函數作為參數或指派給變量的場景,避免了顯式地定義命名函數的過程。在函數式程式設計中,匿名函數常常用于實作高階函數,即函數接受一個或多個函數作為參數或傳回一個函數。
- Lambda表達式: Lambda表達式是一種輕量級的匿名函數文法,它允許我們以更簡潔、更易讀的方式編寫匿名函數。Lambda表達式是函數式程式設計的一個重要特性,它可以用來替代冗長的匿名内部類。Lambda表達式可以作為參數傳遞給函數或方法,或者指派給一個函數式接口變量。它的文法形式為(parameters) -> expression或(parameters) -> { statements; },其中parameters是參數清單,expression是表達式或語句塊。
函數式程式設計的幾個核心概念和特點
- 函數傳遞:函數可以像任何其他資料類型一樣被傳遞給其他函數,可以被指派給變量,可以存儲在資料結構中,也可以作為函數的傳回值。
- 純函數(Pure Functions):純函數是指對于相同的輸入,總是産生相同的輸出,并且沒有任何可觀察的副作用。純函數不依賴于外部狀态,不修改傳入的參數,也不引起系統狀态的變化。
- 不可變性(Immutability):函數式程式設計鼓勵使用不可變資料結構,即一旦建立就不能修改的資料結構。因為不可變性可以減少狀态變化的複雜性,避免競态條件和并發問題。
- 引用透明(Referential Transparency):引用透明意味着可以将函數調用替換為函數傳回的結果,而不會影響程式的行為。這種特性使得代碼更易于了解、測試和推理。
- 高階函數(Higher-Order Functions):高階函數是指可以接受一個或多個函數作為參數,并且/或者傳回一個函數的函數。高階函數使得代碼更加抽象和靈活,可以進行函數的組合、變換和延遲執行等操作。
- 聲明式程式設計(Declarative Programming):函數式程式設計更加強調聲明式的程式設計風格,即關注“做什麼”而不是“怎麼做”。通過将計算過程抽象為函數調用群組合,減少了顯式的控制流和狀态管理。
在函數式程式設計中,常用的函數操作包括映射(map)、過濾(filter)、折疊(reduce)等。此外,函數式程式設計還經常使用遞歸、柯裡化、惰性求值等技術來處理問題。
匿名函數和Lambda表達式
匿名函數是一種不需要命名的函數,可以直接在需要的地方定義和使用,而不必為其指定一個獨立的名稱。它通常用于需要傳遞函數作為參數或指派給變量的場景,避免了顯式地定義命名函數的過程。
匿名函數的優點在于它簡化了代碼的結構,減少了備援的定義和命名過程。它使得代碼更加緊湊,更易于了解和閱讀。匿名函數常用于函數式程式設計、事件處理、回調函數等場景。
在許多程式設計語言中,匿名函數的文法形式通常是使用關鍵字或特殊符号來表示匿名函數的開始和結束,并提供參數清單和函數體。
在Java中,Lambda表達式是一種匿名函數的形式。它的文法形式為(parameters) -> expression或(parameters) -> { statements; },其中parameters是參數清單,expression是單個表達式或者{ statements; }是多條語句組成的函數體。
以下是一個簡單的Java Lambda表達式的示例:
Runnable runnable = () -> {
System.out.println("This is an example of an anonymous function.");
System.out.println("It is defined using a Lambda expression.");
};
在上述示例中,我們使用Lambda表達式建立了一個匿名函數,并将其指派給一個Runnable接口變量。Lambda表達式的函數體包含了兩條列印語句。
Lambda表達式:
(parameters) -> expression
或
(parameters) -> { statements; }
其中,parameters是參數清單,expression是表達式或語句塊。
Lambda表達式省略了方法的名稱和傳回類型,隻關注參數和執行邏輯。它可以用來替代匿名内部類,并且更加簡潔和易讀。
下面是一個使用Lambda表達式的匿名函數的示例:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
示例中使用forEach方法和Lambda表達式周遊列印names清單中的每個元素。Lambda表達式name -> System.out.println(name)代表一個接受一個參數并執行列印操作的匿名函數。
Lambda表達式使用場景
- 函數式接口:Lambda表達式适用于函數式接口,即隻有一個抽象方法的接口。在這種情況下,可以使用Lambda表達式作為接口的實作。
- 集合操作:Lambda表達式可以友善地應用于集合操作,如篩選、映射、排序等。通過使用Lambda表達式,可以以一種簡潔、清晰的方式對集合中的元素進行處理和操作。
- 并行處理:Lambda表達式與Java 8引入的Stream API結合使用,可以友善地進行并行處理。通過将任務分解為多個子任務并并行處理,可以提高代碼的性能。
- 事件監聽器:Lambda表達式可以簡化事件監聽器的實作。通過将Lambda表達式作為事件處理器,可以更直覺地表達事件的處理邏輯,而無需顯式地編寫匿名内部類。
- 線程和并發程式設計:Lambda表達式可以用于簡化線程和并發程式設計。通過将任務封裝為Lambda表達式,可以更友善地實作多線程和并發操作。
- 表達式求值:Lambda表達式可以用于實作動态表達式求值,比如在電腦應用中解析和計算表達式。
Lambda表達式例子:
- 資料轉換和處理:使用函數式程式設計可以輕松地對資料進行轉換和處理。例如,使用map操作可以對集合中的每個元素應用同一個函數,将其轉換為新的元素。這種資料轉換非常适合函數式程式設計的風格。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubledNumbers = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
在上述示例中,使用map操作将清單中的每個元素都乘以2,并收集到一個新的清單中。
- 過濾和篩選:函數式程式設計非常适合對資料進行過濾和篩選的場景。使用filter操作可以根據某個條件過濾出滿足條件的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
在上述示例中,使用filter操作篩選出清單中的偶數。
- 并行處理:函數式程式設計鼓勵無副作用和不可變性的特性,使得代碼更容易進行并行處理。通過使用并行流,可以将計算任務配置設定給多個處理器核心并行執行,提高處理效率。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
在上述示例中,使用并行流對清單中的數字進行求和操作,可以在多個處理器核心上并行執行。
- 延遲執行:函數式程式設計支援延遲執行的特性,隻有在需要結果時才進行實際的計算。這種特性可以提高性能,避免不必要的計算。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> doubledNumbers = numbers.stream()
.map(n -> n * 2);
// 省略其他操作
int sum = doubledNumbers.reduce(0, Integer::sum);
在上述示例中,map操作會生成一個延遲執行的Stream,隻有在調用reduce操作時才會進行實際的計算。
Stream、filter和collect是Java中的函數式程式設計特性和API,屬于java.util.stream包。
- Stream是Java 8引入的一個用于處理集合資料的API。它提供了一種流式處理資料的方式,可以進行轉換、過濾、映射、排序等操作。Stream API提供了豐富的方法來處理資料流,如map、filter、reduce等,使得代碼更加簡潔和易讀。
- filter是Stream接口中的一個中間操作方法,用于過濾流中的元素。它接受一個Predicate函數式接口作為參數,用于判斷元素是否滿足條件。隻有滿足條件的元素會被保留下來,組成一個新的流。
- collect是Stream接口中的一個終止操作方法,用于将流中的元素收集到一個集合或其他資料結構中。它接受一個Collector參數,用于定義收集的方式。常見的收集器包括Collectors.toList()、Collectors.toSet()等,用于将元素收集到List或Set中。
這些函數式程式設計的方法和API使得在Java中進行集合資料處理變得更加友善和靈活。它們屬于Java标準庫中的java.util.stream包,提供了豐富的功能和操作,幫助我們更好地處理和轉換集合資料。擴充下,資料類型有stream()方法的類型,它們具有stream()方法:
- 集合類(Collections):
- List 接口的實作類(如 ArrayList、LinkedList)
- Set 接口的實作類(如 HashSet、LinkedHashSet)
- Queue 接口的實作類(如 ArrayDeque、LinkedList)
- 數組(Array):
- 通過Arrays.stream()方法可以将數組轉換為流。
- Map 接口的實作類:
- Map 接口的實作類(如 HashMap、TreeMap)
- ConcurrentMap 接口的實作類(如 ConcurrentHashMap)
- IO 流(IO Streams):
- java.io.BufferedReader、java.io.FileReader 等讀取檔案的類
- 字元串(String):
- String 類提供了chars()、codePoints()等方法來建立字元流。
更具體的例子:
假設我們有一個包含一組人員的清單,我們希望從中篩選出年齡大于等于18歲的成年人,并将他們的姓名收集到一個新的清單中。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 17),
new Person("Charlie", 30),
new Person("David", 20)
);
List<String> adultNames = people.stream()
.filter(person -> person.getAge() >= 18)
.map(Person::getName)
.collect(Collectors.toList());
System.out.println(adultNames); // 輸出: [Alice, Charlie, David]
}
}
在上述例子中,建立了一個Person類來表示人員,包含姓名和年齡屬性。
通過使用Stream,可以将people清單轉換為一個資料流。然後,使用filter方法篩選出年齡大于等于18歲的成年人,即滿足條件 person -> person.getAge() >= 18 的人。
接下來,我們使用map方法将每個成年人的姓名映射為一個新的流。Person::getName 是一個方法引用,表示使用Person對象的getName方法來擷取姓名。
最後,使用collect方法将流中的姓名收集到一個新的清單中,使用Collectors.toList()收集器來建立一個List對象。
最終,列印輸出了篩選出的成年人的姓名清單 [Alice, Charlie, David]。
通過這個例子,可以看到使用Stream、filter和collect的過程:首先,使用Stream将資料轉換為流;然後使用filter對流中的元素進行篩選;接着使用map對流中的元素進行映射;最後使用collect将流中的元素收集到一個新的集合中。
這種函數式程式設計的方式使得代碼更加簡潔、可讀性更高,并且提供了一種聲明式的方式來處理集合資料(更加簡潔、可讀性更高是官方說法,我是不認同的。那天我突然想改下邏輯,那一坨代碼我就要全部改,同時又擔心改了會不會改到其他人最先設定的一些其他邏輯,頭疼與糾結)。