Java-lambda表達(dá)式入門看這一篇就夠了
概述
Lambda表達(dá)式,也可稱為閉包,是JDK8的新特性。Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中),可以使代碼變的更加簡潔緊湊。Lambda表達(dá)式是一個可傳遞的代碼塊,可以在以后執(zhí)行一次或多次。
名字起源是以前還沒有計算機(jī)時,邏輯學(xué)家Alonzo Church想要形式化的表示能有效計算的數(shù)學(xué)函數(shù),使用了希臘字母lambda( λ \lambda λ)來標(biāo)記參數(shù),從那以后,帶參數(shù)變量的表達(dá)式就被稱為lambda表達(dá)式。
lambda表達(dá)式本質(zhì)是一個匿名函數(shù),比如以下函數(shù)
public int add(int x, int y) { return x + y; }
可以轉(zhuǎn)換為:
(int x, int y) -> x + y;
語法
語法格式如下
(parameters) -> expression 或 (parameters) ->{ statements; }
其中()用來描述參數(shù)列表,{}用來描述方法體,->是lambda運算符,讀作goes to。
可以包含顯示的return語句,如:
(String sirst,String second)-> { if(first.length()<second.length())return -1; else if(first.length()>second.length())return 1; else return 0; }
可以沒有參數(shù),但()不可缺?。?br />
()->{for(int i=0;i<10;i++)System.out.print(i);}
如果可以推導(dǎo)出參數(shù)類型,則可以忽略其類型:
Comparator<String>cmp=(first,second)->first.length()-second.length();
特別注意不能只在某些分支返回一個值,這是不合法的,如:
(int x)->{if(x>=0)return 1;}
常用示例:
ArrayList<Integer>list=new ArrayList<>(); Collections.addAll(list,1,2,3,4,5); //遍歷 list.forEach(e->{System.out.println(e);}); //刪除指定值 list.removeIf(e->e==3); //排序 list.sort((o1,o2)->o2-o1); //遍歷(雙冒號操作符) list.forEach(System.out::println);
函數(shù)式接口
Java中又很多封裝代碼塊的接口,如ActionListener
、Comparator
等,lambda表達(dá)式與這些接口時兼容的。
對于只有一個抽象方法的接口,需要這種接口的對象時,就可以提供一個lambda表達(dá)式,這種接口稱為函數(shù)式接口。
比如Arrays.sort()
方法,它的第二個參數(shù)需要一個Comparator
實例,而Comparator
就是只有一個方法的接口,所以可以使用lambda表達(dá)式替代,可以把lambda表達(dá)式看作一個函數(shù),而不是一個對象,如:
Arrays.sort(arrays, (first,second)->first.length()-second.length());
lambda表達(dá)式還可以轉(zhuǎn)換為接口,比如實現(xiàn)Runnable接口:
new Thread(() -> System.out.println("記得一鍵三連")).start(); Runnable r = () -> System.out.println("(。・∀・)ノ"); r.run();
再如之前提到的removeIf()
方法,它的參數(shù)就是一個Predicate
接口(位于java.util.function包),這個接口專門用來傳遞lambda表達(dá)式,如刪除一個數(shù)組列表所有null值:
list.removeIf(e->e==null)
方法引用
當(dāng)在Lambda表達(dá)式中直接調(diào)用了一個方法時可以使用,其寫法為目標(biāo)引用::方法名稱。
有時候,可能已經(jīng)有現(xiàn)成的方法可以完成你想要傳遞到其他代碼的某個動作,如遍歷打印集合:
list.forEach(e->{System.out.println(e);});
我們可以直接把現(xiàn)成的println方法傳遞給它:
list.forEach(System.out::println);
它們是等價的,是一個方法引用的寫法。
再如對字符串排序而不考慮大小寫,可以直接傳遞以下方法表達(dá)式:
Arrays.sort(strings,String::compareToIgnoreCase);
也就是說使用雙冒號操作符::來分離方法名與對象或類名:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
對于前兩種情況,方法引用等價于提供方法參數(shù)的lambda表達(dá)式,如,Math::Pow等價于(x,y)->Math.pos(x,y)。
對于第三種情況,第一個參數(shù)會成為方法的目標(biāo),如String::compareToIgnoreCase等同于(x,y)->x.compareToIgnoreCase(y)。
也可以在方法中引用this參數(shù),如this::equals等價于x->this.equals(x),同樣的,使用super也是允許的。
(
插播反爬信息)博主CSDN地址:https://wzlodq.blog.csdn.net/
構(gòu)造器引用
構(gòu)造器引用與方法引用很類似,只不過方法名為new
,例如Person::new是Person類構(gòu)造器的一個引用,如果有多個構(gòu)造器,編譯器會取決于上下文。比如有一個字符串列表,可以把他轉(zhuǎn)換為一個Person對象數(shù)組,為此要在各個字符串上調(diào)用構(gòu)造器。
可以用數(shù)組類型建立構(gòu)造器引用,如int[]::new是一個構(gòu)造器引用,它有一個參數(shù)即數(shù)組長度,等價于lambda表達(dá)式:x->new int[x];
在Java中無法構(gòu)造泛型類型T的數(shù)組,而數(shù)組構(gòu)造器引用就可克服這個限制。如表達(dá)式new T[n]會產(chǎn)生錯誤,因為這會改為new Object[n]。設(shè)我們需要一個Person對象數(shù)組,Stream接口有一個toArray方法可以返回Object數(shù)組:
Object[] people=stream.toArray();
以上得到的是一個Object引用數(shù)組,可以把Person[]::new傳入給toArray()方法,從而得到一個Person對象數(shù)組:
Object[] people=stream.toArray(Person[]::new);
變量作用域
有時候,我們希望能夠在lambda表達(dá)式中訪問外圍方法或類中的變量,如下面例子:
public static void printTip(String text) { Runnable r = () -> System.out.println(text); r.run(); } public static void main(String[] args) { String text="一鍵三連"; printTip(text); }
現(xiàn)在來看lambda表達(dá)式中的text變量,它并不是在這個lambda表達(dá)式中定義的,實際上是printTip方法的一個參數(shù)變量。但仔細(xì)想想,lambda表達(dá)式的代碼可能會在printTip調(diào)用返回很久以后才運行,而那時這個參數(shù)變量已經(jīng)不存在了,如何保留text變量?
首先鞏固瞎lambda表達(dá)式的理解,lmabda表達(dá)式有3個部分:一個代碼塊、變量、自由變量的值(指非參數(shù)而且不在代碼中定義的變量)。在上面的例子中,lambda表達(dá)式有一個自由變量text,表示lambda表達(dá)式的數(shù)據(jù)結(jié)構(gòu)必須存儲的值(這里的字符串“一鍵三連”),我們稱它被lambda表達(dá)式捕獲(captured)。
關(guān)于代碼塊和自由變量值有一個術(shù)語:閉包(closure),在Java中,lambda表達(dá)式就是閉包。
lambda表達(dá)式捕獲變量必須遵循的規(guī)則:捕獲的變量必須實際上必須是最終變量(effectively final),最終變量是指這個變量初始化之后就不會再為它賦新值,即在lambda表達(dá)式內(nèi)外都不能在修改值。
如lambda內(nèi)修改:
public static void printTip(String text) { Runnable r = () -> { System.out.println(text); text="修改值會報錯"; }; r.run(); } public static void main(String[] args) { String text="一鍵三連"; printTip(text); }
lambda外修改:
public static void main(String[] args) { for(int i=0;i<5;i++){ //表達(dá)式外修改變量i報錯 Runnable r = () -> System.out.println(i); r.run(); } }
此外,在方法中不能有同名的局部變量,lambda表達(dá)式也是如此:
public static void main(String[] args) { int first=666; //同名會報錯 Comparator<String> cmp= (first,second)->first.length()-second.length(); }
對于lambda表達(dá)式中使用this關(guān)鍵字時,是指創(chuàng)建這個lambda表達(dá)式的方法餓this參數(shù)。
處理lambda表達(dá)式
使用lambda表達(dá)式的重點是延遲執(zhí)行(deferred execution)。如果要立即執(zhí)行代碼的畫完全可以直接執(zhí)行而無需放到一個lambda表達(dá)式中,之所以希望以后在執(zhí)行代碼,這有很多原因,如:
- 在一個單獨的線程中運行代碼
- 多次運行代碼
- 在算法的適當(dāng)位置運行代碼
- 發(fā)生某種情況時執(zhí)行代碼
- 只有在必要時才運行代碼
設(shè)想要重復(fù)一個動作n次,將這個動作和重復(fù)次數(shù),傳遞到一個repeat方法,要接受這個lambda表達(dá)式需要選擇一個函數(shù)式接口,在這里我們可以使用Runnable接口,后面給出Java API中提供的最重要的函數(shù)式接口。
public static void repeat(int n,Runnable action){ for(int i=0;i<n;i++) action.run(); } public static void main(String[] args) { repeat(10,()-> System.out.println("一鍵三連")); }
函數(shù)式接口
函數(shù)式接口 | 參數(shù)類型 | 返回類型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 無 | void | run | 作為無參數(shù)或返回值的動作執(zhí)行 | |
Supplier<T> | 無 | T | get | 提供一個T類型的值 | |
Consumer<T> | T | void | accept | 處理一個T類型的值 | addThen |
BiConsumer<T,U> | T,U | void | accept | 處理T和U類型的值 | addThen |
Function<T,R> | T | R | apply | 有一個T類型參數(shù)的函數(shù) | compose,addThen,idenity |
BiFunction<T,U,R> | T,U | R | apply | 有T和U類型參數(shù)的函數(shù) | addThen |
UnaryOperator<T> | T | T | apply | 類型T上的一元操作符 | compose,addThen,identity |
BinaryOperator<T> | T,T | T | apply | 類型T上的二元操作符 | addThen,maxBy,minBy |
PreDicate<T> | T | boolean | test | 布爾值函數(shù) | add,or,negate,isEqual |
BiPredicate | T,U | boolean | test | 有兩個參數(shù)的布爾值函數(shù) | add,or,negate |
現(xiàn)在讓這個例子更復(fù)雜一些,我們希望知道這個動作出現(xiàn)在哪一次迭代中。為此需要選擇一個合適的函數(shù)式接口,其中要包含一個方法,這個方法有一個int參數(shù)而且返回類型為void,如下所示:
public interface IntConsumer{ void accept(int value); } public static void repeat(int n,IntConsumer action){ for(int i=0;i<n;i++) action.accept(i); } public static void main(String[] args) { repeat(10,i-> System.out.println(9-i)); }
下表列出了基本類型int、long和double的34個可能的規(guī)范,最好使用這些特殊化規(guī)范來減少自動裝箱:
函數(shù)式接口 | 參數(shù)類型 | 返回類型 | 抽象方法名 |
---|---|---|---|
BooleanSupplier | none | boolean | getAsBoolean |
P P PSupplier | none | p p p | getAs P P P |
P P PConsumer | p p p | void | accept |
Obj P P PConsumer<T> | T, p p p | void | accept |
P P PFunction<T> | p p p | T | apply |
P P PToQFunction | p p p | q q q | applyAs Q Q Q |
To P P PFunction<T> | T | p p p | applyAs P P P |
To P P PBiFunction<T,U> | T,U | p p p | applyAs P P P |
P P PUnaryOperator | p p p | p p p | applyAs P P P |
P P PBinaryOperator | p p p, p p p | p p p | applyAs P P P |
P P PPredicate | p p p | boolean | test |
p p p, q q q為int,long,double;
P P P, Q Q
以上就是Java-lambda表達(dá)式入門看這一篇就夠了的詳細(xì)內(nèi)容,更多關(guān)于Java-lambda表達(dá)式入門的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringSecurity 手機(jī)號登錄功能實現(xiàn)
這篇文章主要介紹了SpringSecurity 手機(jī)號登錄功能實現(xiàn),本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2023-12-12Spring-data-redis操作redis知識總結(jié)
這篇文章主要介紹了Spring-data-redis操作redis知識總結(jié),spring-data-redis是spring-data模塊的一部分,專門用來支持在spring管理項目對redis的操作。2017-04-04SpringCloud FeignClient 超時設(shè)置
FeignClient?默認(rèn)的超時時間可能不滿足你的需求,你可以通過幾種方式來自定義這些超時設(shè)置,具有一定的參考價值,感興趣的可以了解一下2024-08-08一小時迅速入門Mybatis之Prepared Statement與符號的使用
這篇文章主要介紹了一小時迅速入門Mybatis之Prepared Statement與符號的使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09使用java swing實現(xiàn)qq登錄界面示例分享
這篇文章主要介紹了使用java swing實現(xiàn)qq登錄界面示例,需要的朋友可以參考下2014-04-04Java 中POI 導(dǎo)入EXCEL2003 和EXCEL2007的實現(xiàn)方法
這篇文章主要介紹了Java 中POI 導(dǎo)入EXCEL2003 和EXCEL2007的實現(xiàn)方法的相關(guān)資料,希望通過本文大家能掌握理解這種方法,需要的朋友可以參考下2017-09-09SpringBoot、mybatis返回樹結(jié)構(gòu)的數(shù)據(jù)實現(xiàn)
本文主要介紹了SpringBoot、mybatis返回樹結(jié)構(gòu)的數(shù)據(jù)實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04