Java Lambda 表達式是Java 8 引入的一個新的功能,可以說是模擬函數式編程的一個語法糖,類似於Javascript 中的閉包,但又有些不同,主要目的是提供一個函數化的語法來簡化我們的編碼。
Lambda 基本語法
Lambda 的基本結構為(arguments) -> body,有如下幾種情況:
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 8 中更是新增加了一個包java.util.function,帶來了常用的函數式接口:
另外還對基本類型的處理增加了更加具體的函數是接口,包括: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);
編譯期間,我理解的類型推導的過程如下:
這裡的目標類型是關鍵,通過目標類型獲取方法簽名,然後和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 表達式確實可以減少很多代碼,能提高生產力,當然也有弊端,就是複雜的表達式可讀性會比較差,也可能是還不是很習慣的緣故吧,如果習慣了,相信會喜歡上的。凡事都有兩面性,就看我們如何去平衡這其中的利弊了,尤其是在一個團隊中。
以上就是對Java8 JavaLambda 的資料整理,後續繼續補充相關資料謝謝大家對本站的支持!