Java Lambda 表示式學習筆記
Java Lambda 表示式是 Java 8 引入的一個新的功能,可以說是模擬函數語言程式設計的一個語法糖,類似於 Javascript 中的閉包,但又有些不同,主要目的是提供一個函式化的語法來簡化我們的編碼。
Lambda 基本語法
Lambda 的基本結構為 (arguments) -> body
,有如下幾種情況:
- 引數型別可推導時,不需要指定型別,如
(a) -> System.out.println(a)
- 當只有一個引數且型別可推導時,不強制寫
()
, 如a -> System.out.println(a)
- 引數指定型別時,必須有括號,如
(int a) -> System.out.println(a)
- 引數可以為空,如
() -> System.out.println(“hello”)
- body 需要用
{}
包含語句,當只有一條語句時{}
可省略
常見的寫法如下:
(a) -> a * a (int a, int b) -> a + b (a, b) -> {return a - b;} () -> System.out.println(Thread.currentThread().getId())
函式式介面 FunctionalInterface
概念
Java Lambda 表示式以函式式介面為基礎。什麼是函式式介面(FunctionalInterface)? 簡單說來就是隻有一個方法(函式)的介面,這類介面的目的是為了一個單一的操作,也就相當於一個單一的函式了。常見的介面如:Runnable, Comparator 都是函式式介面,並且都標註了註解 @FunctionalInterface
。
舉例
以 Thread 為例說明很容易理解。Runnable 介面是我們執行緒程式設計時常用的一個介面,就包含一個方法 void run()
,這個方法就是執行緒的執行邏輯。按照以前的語法,我們新建執行緒一般要用到 Runnable 的匿名類,如下:
new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getId()); } }).start();
如果寫多了,是不是很無聊,而基於 Lambda 的寫法則變得簡潔明瞭,如下:
new Thread(() -> System.out.println(Thread.currentThread().getId())).start();
注意 Thread 的引數,Runnable 的匿名實現就通過一句就實現了出來,寫成下面的更好理解
Runnable r = () -> System.out.println(Thread.currentThread().getId()); new Thread(r).start();
當然 Lambda 的目的不僅僅是寫起來簡潔,更高層次的目的等體會到了再總結。
再看一個比較器的例子,按照傳統的寫法,如下:
Integer[] a = {1, 8, 3, 9, 2, 0, 5}; Arrays.sort(a, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } });
Lambda 表示式寫法如下:
Integer[] a = {1, 8, 3, 9, 2, 0, 5}; Arrays.sort(a, (o1, o2) -> o1 - o2);
JDK中的函式式介面
為了現有的類庫能夠直接使用 Lambda 表示式,Java 8 以前存在一些介面已經被標註為函式式介面的:
java.lang.Runnable
java.util.Comparator
java.util.concurrent.Callable
java.io.FileFilter
java.security.PrivilegedAction
java.beans.PropertyChangeListener
Java 8 中更是新增加了一個包 java.util.function
,帶來了常用的函式式介面:
Function<T, R>
- 函式:輸入 T 輸出 RBiFunction<T, U, R>
- 函式:輸入 T 和 U 輸出 R 物件Predicate<T>
- 斷言/判斷:輸入 T 輸出 booleanBiPredicate<T, U>
- 斷言/判斷:輸入 T 和 U 輸出 booleanSupplier<T>
- 生產者:無輸入,輸出 TConsumer<T>
- 消費者:輸入 T,無輸出BiConsumer<T, U>
- 消費者:輸入 T 和 U 無輸出UnaryOperator<T>
- 單元運算:輸入 T 輸出 TBinaryOperator<T>
- 二元運算:輸入 T 和 T 輸出 T
另外還對基本型別的處理增加了更加具體的函式是介面,包括:BooleanSupplier
, DoubleBinaryOperator
, DoubleConsumer
, DoubleFunction<R>
, DoublePredicate
, DoubleSupplier
, DoubleToIntFunction
, DoubleToLongFunction
, DoubleUnaryOperator
, IntBinaryOperator
, IntConsumer
, IntFunction<R>
, IntPredicate
, IntSupplier
, IntToDoubleFunction
, IntToLongFunction
, IntUnaryOperator
, LongBinaryOperator
, LongConsumer
,LongFunction<R>
, LongPredicate
, LongSupplier
, LongToDoubleFunction
,LongToIntFunction
, LongUnaryOperator
, ToDoubleBiFunction<T, U>
, ToDoubleFunction<T>
,ToIntBiFunction<T, U>
, ToIntFunction<T>
, ToLongBiFunction<T, U>
, ToLongFunction<T>
。結合上面的函式式介面,對這些基本型別的函式式介面通過類名就能一眼看出介面的作用。
建立函式式介面
有時候我們需要自己實現一個函式式介面,做法也很簡單,首先你要保證此介面只能有一個函式操作,然後在介面型別上標註註解 @FunctionalInterface
即可。
型別推導
型別推導是 Lambda 表示式的基礎,型別推導的過程就是 Lambda 表示式的編譯過程。以下面的程式碼為例:
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
編譯期間,我理解的型別推導的過程如下:
- 先確定目標型別 Function
- Function 作為函式式介面,其方法簽名為:Integer apply(String t)
- 檢測 str -> Integer.parseInt(str) 是否與方法簽名匹配(方法的引數型別、個數、順序 和返回值型別)
- 如果不匹配,則報編譯錯誤
這裡的目標型別是關鍵,通過目標型別獲取方法簽名,然後和 Lambda 表示式做出對比。
方法引用
方法引用(Method Reference)的基礎同樣是函式式介面,可以直接作為函式式介面的實現,與 Lambda 表示式有相同的作用,同樣依賴於型別推導。方法引用可以看作是隻呼叫一個方法的 Lambda 表示式的簡化。
方法引用的語法為: Type::methodName
或者 instanceName::methodName
, 建構函式對應的 methodName 為 new。
例如上面曾用到例子:
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
對應的方法引用的寫法為
Function<String, Integer> strToInt = Integer::parseInt;
根據方法的型別,方法引用主要分為一下幾種型別,構造方法引用、靜態方法引用、例項上例項方法引用、型別上例項方法引用等
構造方法引用
語法為: Type::new
。 如下面的函式為了將字串轉為陣列
方法引用寫法
Function<String, Integer> strToInt = Integer::new;
Lambda 寫法
Function<String, Integer> strToInt = str -> new Integer(str);
傳統寫法
Function<String, Integer> strToInt = new Function<String, Integer>() { @Override public Integer apply(String str) { return new Integer(str); } };
陣列構造方法引用
語法為: Type[]::new
。如下面的函式為了構造一個指定長度的字串陣列
方法引用寫法
Function<Integer, String[]> fixedArray = String[]::new;
方法引用寫法
Function<Integer, String[]> fixedArray = length -> new String[length];
傳統寫法
Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() { @Override public String[] apply(Integer length) { return new String[length]; } };
靜態方法引用
語法為: Type::new
。 如下面的函式同樣為了將字串轉為陣列
方法引用寫法
Function<String, Integer> strToInt = Integer::parseInt;
Lambda 寫法
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
傳統寫法
Function<String, Integer> strToInt = new Function<String, Integer>() { @Override public Integer apply(String str) { return Integer.parseInt(str); } };
例項上例項方法引用
語法為: instanceName::methodName
。如下面的判斷函式用來判斷給定的姓名是否在列表中存在
List<String> names = Arrays.asList(new String[]{"張三", "李四", "王五"}); Predicate<String> checkNameExists = names::contains; System.out.println(checkNameExists.test("張三")); System.out.println(checkNameExists.test("張四"));
型別上例項方法引用
語法為: Type::methodName
。執行時引用是指上下文中的物件,如下面的函式來返回字串的長度
Function<String, Integer> calcStrLength = String::length; System.out.println(calcStrLength.apply("張三")); List<String> names = Arrays.asList(new String[]{"zhangsan", "lisi", "wangwu"}); names.stream().map(String::length).forEach(System.out::println);
又比如下面的函式已指定的分隔符分割字串為陣列
BiFunction<String, String, String[]> split = String::split; String[] names = split.apply("zhangsan,lisi,wangwu", ","); System.out.println(Arrays.toString(names));
Stream 物件
概念
什麼是 Stream ? 這裡的 Stream 不同於 io 中的 InputStream 和 OutputStream,Stream 位於包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一組支援序列並行聚合操作的元素,可以理解為集合或者迭代器的增強版。什麼是聚合操作?簡單舉例來說常見的有平均值、最大值、最小值、總和、排序、過濾等。
Stream 的幾個特徵:
- 單次處理。一次處理結束後,當前Stream就關閉了。
- 支援並行操作
常見的獲取 Stream 的方式
- 從集合中獲取
- Collection.stream();
- Collection.parallelStream();
- 靜態工廠
- Arrays.stream(array)
- Stream.of(T …)
- IntStream.range()
這裡只對 Stream 做簡單的介紹,下面會有具體的應用。要說 Stream 與 Lambda 表示式有什麼關係,其實並沒有什麼特別緊密的關係,只是 Lambda 表示式極大的方便了 Stream 的使用。如果沒有 Lambda 表示式,使用 Stream 的過程中會產生大量的匿名類,非常彆扭。
舉例
以下的demo依賴於 Employee 物件,以及由 Employee 物件組成的 List 物件。
public class Employee { private String name; private String sex; private int age; public Employee(String name, String sex, int age) { super(); this.name = name; this.sex = sex; this.age = age; } public String getName() { return name; } public String getSex() { return sex; } public int getAge() { return age; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age) .append("}"); return builder.toString(); } }
List<Employee> employees = new ArrayList<>(); employees.add(new Employee("張三", "男", 25)); employees.add(new Employee("李四", "女", 24)); employees.add(new Employee("王五", "女", 23)); employees.add(new Employee("週六", "男", 22)); employees.add(new Employee("孫七", "女", 21)); employees.add(new Employee("劉八", "男", 20));
列印所有員工
Collection 提供了 forEach 方法,供我們逐個操作單個物件。
employees.forEach(e -> System.out.println(e)); 或者 employees.stream().forEach(e -> System.out.println(e));
按年齡排序
Collections.sort(employees, (e1, e2) -> e1.getAge() - e2.getAge()); employees.forEach(e -> System.out.println(e)); 或者 employees.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(e -> System.out.println(e));
列印年齡最大的女員工
max/min 返回指定排序條件下最大/最小的元素
Employee maxAgeFemaleEmployee = employees.stream() .filter(e -> "女".equals(e.getSex())) .max((e1, e2) -> e1.getAge() - e2.getAge()) .get(); System.out.println(maxAgeFemaleEmployee);
列印出年齡大於20 的男員工
filter 可以過濾出符合條件的元素
employees.stream() .filter(e -> e.getAge() > 20 && "男".equals(e.getSex())) .forEach(e -> System.out.println(e));
列印出年齡最大的2名男員工
limit 方法擷取有限的元素
employees.stream() .filter(e -> "男".equals(e.getSex())) .sorted((e1, e2) -> e2.getAge() - e1.getAge()) .limit(2) .forEach(e -> System.out.println(e));
列印出所有男員工的姓名,使用 , 分隔
map 將 Stream 中所有元素的執行給定的函式後返回值組成新的 Stream
String maleEmployeesNames = employees.stream() .map(e -> e.getName()) .collect(Collectors.joining(",")); System.out.println(maleEmployeesNames);
統計資訊
IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics 包含了 Stream 中的彙總資料。
IntSummaryStatistics stat = employees.stream() .mapToInt(Employee::getAge).summaryStatistics(); System.out.println("員工總數:" + stat.getCount()); System.out.println("最高年齡:" + stat.getMax()); System.out.println("最小年齡:" + stat.getMin()); System.out.println("平均年齡:" + stat.getAverage());
總結
Lambda 表示式確實可以減少很多程式碼,能提高生產力,當然也有弊端,就是複雜的表示式可讀性會比較差,也可能是還不是很習慣的緣故吧,如果習慣了,相信會喜歡上的。凡事都有兩面性,就看我們如何去平衡這其中的利弊了,尤其是在一個團隊中。
相關文章
- Java筆記:Lambda表示式Java筆記
- Python學習筆記 - lambda表示式Python筆記
- 工作學習筆記(十一)Lambda 表示式筆記
- C++11 學習筆記 lambda表示式筆記
- [Java學習筆記]JDK1.8新特性學習(一)Lambda表示式Java筆記JDK
- java8學習:lambda表示式(2)Java
- java8學習:lambda表示式(1)Java
- Java 8 Lambda 表示式學習心得總結Java
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- Java Lambda表示式Java
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- 最近學習到的Lambda表示式
- 正規表示式學習筆記筆記
- angular學習筆記(十一)-表示式Angular筆記
- Java之lambda表示式Java
- Java的Lambda表示式Java
- Java 8 Lambda 表示式Java
- java 8 lambda表示式Java
- Java Lambda 表示式初探Java
- JDK1.8 Lambda 表示式的學習JDK
- 禿頭星人進化記(Lambda表示式學習總結)
- 深圳Java培訓學習:Java8.0新特性之Lambda表示式Java
- Ruby學習筆記-正規表示式筆記
- 正規表示式學習筆記一筆記
- PERL學習筆記---正規表示式筆記
- 正規表示式學習筆記 (轉)筆記
- Java 基礎 —— Lambda 表示式Java
- Java 中的 Lambda 表示式Java
- Java lambda表示式基本使用Java
- Java8-Lambda表示式Java
- java8 lambda表示式Java
- 掌握 Java 8 Lambda 表示式Java
- JavaScript正規表示式學習筆記(一)JavaScript筆記
- Python學習筆記 - 正規表示式Python筆記
- Java8的Lambda表示式Java
- Java中lambda表示式詳解Java