Java Lambda 表達(dá)式詳解及示例代碼
Java Lambda 表達(dá)式是 Java 8 引入的一個(gè)新的功能,可以說(shuō)是模擬函數(shù)式編程的一個(gè)語(yǔ)法糖,類(lèi)似于 Javascript 中的閉包,但又有些不同,主要目的是提供一個(gè)函數(shù)化的語(yǔ)法來(lái)簡(jiǎn)化我們的編碼。
Lambda 基本語(yǔ)法
Lambda 的基本結(jié)構(gòu)為 (arguments) -> body,有如下幾種情況:
- 參數(shù)類(lèi)型可推導(dǎo)時(shí),不需要指定類(lèi)型,如 (a) -> System.out.println(a)
- 當(dāng)只有一個(gè)參數(shù)且類(lèi)型可推導(dǎo)時(shí),不強(qiáng)制寫(xiě) (), 如 a -> System.out.println(a)
- 參數(shù)指定類(lèi)型時(shí),必須有括號(hào),如 (int a) -> System.out.println(a)
- 參數(shù)可以為空,如 () -> System.out.println(“hello”)
body 需要用 {} 包含語(yǔ)句,當(dāng)只有一條語(yǔ)句時(shí) {} 可省略
常見(jiàn)的寫(xiě)法如下:
(a) -> a * a
(int a, int b) -> a + b
(a, b) -> {return a - b;}
() -> System.out.println(Thread.currentThread().getId())
函數(shù)式接口 FunctionalInterface
概念
Java Lambda 表達(dá)式以函數(shù)式接口為基礎(chǔ)。什么是函數(shù)式接口(FunctionalInterface)? 簡(jiǎn)單說(shuō)來(lái)就是只有一個(gè)方法(函數(shù))的接口,這類(lèi)接口的目的是為了一個(gè)單一的操作,也就相當(dāng)于一個(gè)單一的函數(shù)了。常見(jiàn)的接口如:Runnable, Comparator 都是函數(shù)式接口,并且都標(biāo)注了注解 @FunctionalInterface 。
舉例
以 Thread 為例說(shuō)明很容易理解。Runnable 接口是我們線程編程時(shí)常用的一個(gè)接口,就包含一個(gè)方法 void run(),這個(gè)方法就是線程的運(yùn)行邏輯。按照以前的語(yǔ)法,我們新建線程一般要用到 Runnable 的匿名類(lèi),如下:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId());
}
}).start();
如果寫(xiě)多了,是不是很無(wú)聊,而基于 Lambda 的寫(xiě)法則變得簡(jiǎn)潔明了,如下:
new Thread(() -> System.out.println(Thread.currentThread().getId())).start();
注意 Thread 的參數(shù),Runnable 的匿名實(shí)現(xiàn)就通過(guò)一句就實(shí)現(xiàn)了出來(lái),寫(xiě)成下面的更好理解
Runnable r = () -> System.out.println(Thread.currentThread().getId());
new Thread(r).start();
當(dāng)然 Lambda 的目的不僅僅是寫(xiě)起來(lái)簡(jiǎn)潔,更高層次的目的等體會(huì)到了再總結(jié)。
再看一個(gè)比較器的例子,按照傳統(tǒng)的寫(xiě)法,如下:
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 表達(dá)式寫(xiě)法如下:
Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, (o1, o2) -> o1 - o2);
JDK中的函數(shù)式接口
為了現(xiàn)有的類(lèi)庫(kù)能夠直接使用 Lambda 表達(dá)式,Java 8 以前存在一些接口已經(jīng)被標(biāo)注為函數(shù)式接口的:
- java.lang.Runnable
- java.util.Comparator
- java.util.concurrent.Callable
- java.io.FileFilter
- java.security.PrivilegedAction
- java.beans.PropertyChangeListener
Java 8 中更是新增加了一個(gè)包 java.util.function,帶來(lái)了常用的函數(shù)式接口:
- Function<T, R> - 函數(shù):輸入 T 輸出 R
- BiFunction<T, U, R> - 函數(shù):輸入 T 和 U 輸出 R 對(duì)象
- Predicate<T> - 斷言/判斷:輸入 T 輸出 boolean
- BiPredicate<T, U> - 斷言/判斷:輸入 T 和 U 輸出 boolean
- Supplier<T> - 生產(chǎn)者:無(wú)輸入,輸出 T
- Consumer<T> - 消費(fèi)者:輸入 T,無(wú)輸出
- BiConsumer<T, U> - 消費(fèi)者:輸入 T 和 U 無(wú)輸出
- UnaryOperator<T> - 單元運(yùn)算:輸入 T 輸出 T
- BinaryOperator<T> - 二元運(yùn)算:輸入 T 和 T 輸出 T
另外還對(duì)基本類(lèi)型的處理增加了更加具體的函數(shù)是接口,包括: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> 。結(jié)合上面的函數(shù)式接口,對(duì)這些基本類(lèi)型的函數(shù)式接口通過(guò)類(lèi)名就能一眼看出接口的作用。
創(chuàng)建函數(shù)式接口
有時(shí)候我們需要自己實(shí)現(xiàn)一個(gè)函數(shù)式接口,做法也很簡(jiǎn)單,首先你要保證此接口只能有一個(gè)函數(shù)操作,然后在接口類(lèi)型上標(biāo)注注解 @FunctionalInterface 即可。
類(lèi)型推導(dǎo)
類(lèi)型推導(dǎo)是 Lambda 表達(dá)式的基礎(chǔ),類(lèi)型推導(dǎo)的過(guò)程就是 Lambda 表達(dá)式的編譯過(guò)程。以下面的代碼為例:
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
編譯期間,我理解的類(lèi)型推導(dǎo)的過(guò)程如下:
- 先確定目標(biāo)類(lèi)型 Function
- Function 作為函數(shù)式接口,其方法簽名為:Integer apply(String t)
- 檢測(cè) str -> Integer.parseInt(str) 是否與方法簽名匹配(方法的參數(shù)類(lèi)型、個(gè)數(shù)、順序 和返回值類(lèi)型)
- 如果不匹配,則報(bào)編譯錯(cuò)誤
這里的目標(biāo)類(lèi)型是關(guān)鍵,通過(guò)目標(biāo)類(lèi)型獲取方法簽名,然后和 Lambda 表達(dá)式做出對(duì)比。
方法引用
方法引用(Method Reference)的基礎(chǔ)同樣是函數(shù)式接口,可以直接作為函數(shù)式接口的實(shí)現(xiàn),與 Lambda 表達(dá)式有相同的作用,同樣依賴于類(lèi)型推導(dǎo)。方法引用可以看作是只調(diào)用一個(gè)方法的 Lambda 表達(dá)式的簡(jiǎn)化。
方法引用的語(yǔ)法為: Type::methodName 或者 instanceName::methodName , 構(gòu)造函數(shù)對(duì)應(yīng)的 methodName 為 new。
例如上面曾用到例子:
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
對(duì)應(yīng)的方法引用的寫(xiě)法為
Function<String, Integer> strToInt = Integer::parseInt;
根據(jù)方法的類(lèi)型,方法引用主要分為一下幾種類(lèi)型,構(gòu)造方法引用、靜態(tài)方法引用、實(shí)例上實(shí)例方法引用、類(lèi)型上實(shí)例方法引用等
構(gòu)造方法引用
語(yǔ)法為: Type::new 。 如下面的函數(shù)為了將字符串轉(zhuǎn)為數(shù)組
方法引用寫(xiě)法
Function<String, Integer> strToInt = Integer::new;
Lambda 寫(xiě)法
Function<String, Integer> strToInt = str -> new Integer(str);
傳統(tǒng)寫(xiě)法
Function<String, Integer> strToInt = new Function<String, Integer>() {
@Override
public Integer apply(String str) {
return new Integer(str);
}
};
數(shù)組構(gòu)造方法引用
語(yǔ)法為: Type[]::new 。如下面的函數(shù)為了構(gòu)造一個(gè)指定長(zhǎng)度的字符串?dāng)?shù)組
方法引用寫(xiě)法
Function<Integer, String[]> fixedArray = String[]::new;
方法引用寫(xiě)法
Function<Integer, String[]> fixedArray = length -> new String[length];
傳統(tǒng)寫(xiě)法
Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() {
@Override
public String[] apply(Integer length) {
return new String[length];
}
};
靜態(tài)方法引用
語(yǔ)法為: Type::new 。 如下面的函數(shù)同樣為了將字符串轉(zhuǎn)為數(shù)組
方法引用寫(xiě)法
Function<String, Integer> strToInt = Integer::parseInt;
Lambda 寫(xiě)法
Function<String, Integer> strToInt = str -> Integer.parseInt(str);
傳統(tǒng)寫(xiě)法
Function<String, Integer> strToInt = new Function<String, Integer>() {
@Override
public Integer apply(String str) {
return Integer.parseInt(str);
}
};
實(shí)例上實(shí)例方法引用
語(yǔ)法為: instanceName::methodName 。如下面的判斷函數(shù)用來(lái)判斷給定的姓名是否在列表中存在
List<String> names = Arrays.asList(new String[]{"張三", "李四", "王五"});
Predicate<String> checkNameExists = names::contains;
System.out.println(checkNameExists.test("張三"));
System.out.println(checkNameExists.test("張四"));
類(lèi)型上實(shí)例方法引用
語(yǔ)法為: Type::methodName 。運(yùn)行時(shí)引用是指上下文中的對(duì)象,如下面的函數(shù)來(lái)返回字符串的長(zhǎng)度
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);
又比如下面的函數(shù)已指定的分隔符分割字符串為數(shù)組
BiFunction<String, String, String[]> split = String::split;
String[] names = split.apply("zhangsan,lisi,wangwu", ",");
System.out.println(Arrays.toString(names));
Stream 對(duì)象
概念
什么是 Stream ? 這里的 Stream 不同于 io 中的 InputStream 和 OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一組支持串行并行聚合操作的元素,可以理解為集合或者迭代器的增強(qiáng)版。什么是聚合操作?簡(jiǎn)單舉例來(lái)說(shuō)常見(jiàn)的有平均值、最大值、最小值、總和、排序、過(guò)濾等。
Stream 的幾個(gè)特征:
單次處理。一次處理結(jié)束后,當(dāng)前Stream就關(guān)閉了。
支持并行操作
常見(jiàn)的獲取 Stream 的方式
從集合中獲取
Collection.stream();
Collection.parallelStream();
靜態(tài)工廠
Arrays.stream(array)
Stream.of(T …)
IntStream.range()
這里只對(duì) Stream 做簡(jiǎn)單的介紹,下面會(huì)有具體的應(yīng)用。要說(shuō) Stream 與 Lambda 表達(dá)式有什么關(guān)系,其實(shí)并沒(méi)有什么特別緊密的關(guān)系,只是 Lambda 表達(dá)式極大的方便了 Stream 的使用。如果沒(méi)有 Lambda 表達(dá)式,使用 Stream 的過(guò)程中會(huì)產(chǎn)生大量的匿名類(lèi),非常別扭。
舉例
以下的demo依賴于 Employee 對(duì)象,以及由 Employee 對(duì)象組成的 List 對(duì)象。
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 方法,供我們逐個(gè)操作單個(gè)對(duì)象。
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 可以過(guò)濾出符合條件的元素
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 中所有元素的執(zhí)行給定的函數(shù)后返回值組成新的 Stream
String maleEmployeesNames = employees.stream()
.map(e -> e.getName())
.collect(Collectors.joining(","));
System.out.println(maleEmployeesNames);
統(tǒng)計(jì)信息
IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics 包含了 Stream 中的匯總數(shù)據(jù)。
IntSummaryStatistics stat = employees.stream()
.mapToInt(Employee::getAge).summaryStatistics();
System.out.println("員工總數(shù):" + stat.getCount());
System.out.println("最高年齡:" + stat.getMax());
System.out.println("最小年齡:" + stat.getMin());
System.out.println("平均年齡:" + stat.getAverage());
總結(jié)
Lambda 表達(dá)式確實(shí)可以減少很多代碼,能提高生產(chǎn)力,當(dāng)然也有弊端,就是復(fù)雜的表達(dá)式可讀性會(huì)比較差,也可能是還不是很習(xí)慣的緣故吧,如果習(xí)慣了,相信會(huì)喜歡上的。凡事都有兩面性,就看我們?nèi)绾稳テ胶膺@其中的利弊了,尤其是在一個(gè)團(tuán)隊(duì)中。
以上就是對(duì)Java8 JavaLambda 的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料謝謝大家對(duì)本站的支持!
相關(guān)文章
詳解SpringBoot如何刪除引用jar包中的無(wú)用bean
為了趕速度和直接將之前多模塊的maven項(xiàng)目中的部分模塊,直接以jar包的形式引入到新項(xiàng)目中了,雖然省去了不少開(kāi)發(fā)時(shí)間,導(dǎo)致項(xiàng)目臃腫,啟動(dòng)很慢。本文將用@ComponentScan注解去實(shí)現(xiàn)讓項(xiàng)目只加載自己需要的bean,需要的可以參考一下2022-06-06
阿里SpringBoot應(yīng)用自動(dòng)化部署實(shí)現(xiàn)IDEA版Jenkins
這篇文章主要為大家介紹了阿里SpringBoot應(yīng)用自動(dòng)化部署實(shí)現(xiàn)IDEA版Jenkins過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Java將科學(xué)計(jì)數(shù)法數(shù)據(jù)轉(zhuǎn)為字符串的實(shí)例
下面小編就為大家?guī)?lái)一篇Java將科學(xué)計(jì)數(shù)法數(shù)據(jù)轉(zhuǎn)為字符串的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
Spring?Security權(quán)限注解啟動(dòng)及邏輯處理使用示例
這篇文章主要為大家介紹了Spring?Security權(quán)限注解啟動(dòng)及邏輯處理使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
java 重定義數(shù)組的實(shí)現(xiàn)方法(與VB的ReDim相像)
java 重定義數(shù)組的實(shí)現(xiàn)方法(與VB的ReDim相像),需要的朋友可以參考一下2013-04-04
mybatis-plus中更新null值的問(wèn)題解決
本文主要介紹 mybatis-plus 中常使用的 update 相關(guān)方法的區(qū)別,以及更新 null 的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04
SpringBoot中的統(tǒng)一異常處理詳細(xì)解析
這篇文章主要介紹了SpringBoot中的統(tǒng)一異常處理詳細(xì)解析,該注解可以把異常處理器應(yīng)用到所有控制器,而不是單個(gè)控制器,借助該注解,我們可以實(shí)現(xiàn):在獨(dú)立的某個(gè)地方,比如單獨(dú)一個(gè)類(lèi),定義一套對(duì)各種異常的處理機(jī)制,需要的朋友可以參考下2024-01-01

