Lambda表达式超详细总结
文章目錄
- 1. 什么是Lambda表達式
- 2. 為什么使用Lambda表達式
- 3. Lambda表達式語法
- 4. 函數式接口
- 4.1 什么是函數式接口
- 4.2 自定義函數式接口
- 4.3 Java內置函數式接口
 
- 5. 方法引用
- 6. 構造器引用
- 7. 數組引用
- 8. Lambda表達式的作用域
- 8.1 訪問局部變量
- 8.2 訪問局部引用,靜態變量,實例變量
- 8.3 Lambda表達式訪問局部變量作限制的原因
 
- 9. Lambda表達式的優缺點
 
1. 什么是Lambda表達式
Lambda表達式,也可稱為閉包。類似于JavaScript中的閉包,它是推動Java8發布的最重要的新特性。
2. 為什么使用Lambda表達式
我們可以把Lambda表達式理解為一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。Lambda允許把函數作為一個方法的參數,使用Lambda表達式可以寫出更簡潔、更靈活的代碼,而其作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。
一個簡單示例:
分別使用成員內部類、局部內部類、靜態內部類、匿名內部類方式實現Runnable的run()方法并創建和啟動線程,如下所示:
public class LambdaDemo {/*** 成員內部類*/class MyThread01 implements Runnable{@Overridepublic void run() {System.out.println("成員內部類:用Lambda語法創建線程吧!");}}/*** 靜態內部類*/static class MyThread02 implements Runnable{@Overridepublic void run() {System.out.println("靜態內部類:對啊,用Lambda語法創建線程吧!");}}public static void main(String[] args) {/*** 局部內部類*/class MyThread03 implements Runnable{@Overridepublic void run() {System.out.println("局部內部類:用Lambda語法創建線程吧!");}}/*** 匿名內部類*/Runnable runnable = new Runnable(){@Overridepublic void run() {System.out.println("匿名內部類:求求你,用Lambda語法創建線程吧!");}};//成員內部類方式LambdaDemo lambdaDemo = new LambdaDemo();MyThread01 myThread01 =lambdaDemo.new MyThread01();new Thread(myThread01).start();//靜態內部類方式MyThread02 myThread02 = new MyThread02();new Thread(myThread02).start();//局部內部類MyThread03 myThread03 = new MyThread03();new Thread(myThread03).start();//匿名內部類的方式new Thread(runnable).start();} }執行結果:
 
可以看到上面創建方式,代碼量都不少,使用Lambda表達式實現,如下所示:
//Lambda方式 new Thread(() -> System.out.println("使用Lambda就對了")).start();可以看到代碼明顯簡潔了許多。
 
3. Lambda表達式語法
Lambda表達式在Java語言中引入了一個操作符**“->”**,該操作符被稱為Lambda操作符或箭頭操作符。它將Lambda分為兩個部分:
- 左側:指定了Lambda表達式需要的所有參數
- 右側:制定了Lambda體,即Lambda表達式要執行的功能。
像這樣:
(parameters) -> expression 或 (parameters) ->{ statements; }以下是lambda表達式的重要特征:
- 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
- 可選的參數圓括號:一個參數無需定義圓括號,但無參數或多個參數需要定義圓括號。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。
下面對每個語法格式的特征進行舉例說明:
(1)語法格式一:無參,無返回值,Lambda體只需一條語句。如下:
@Testpublic void test01(){Runnable runnable=()-> System.out.println("Runnable 運行");runnable.run();//結果:Runnable 運行}(2)語法格式二:Lambda需要一個參數,無返回值。如下:
@Testpublic void test02(){Consumer<String> consumer=(x)-> System.out.println(x);consumer.accept("Hello Consumer");//結果:Hello Consumer}(3)語法格式三:Lambda只需要一個參數時,參數的小括號可以省略,如下:
public void test02(){Consumer<String> consumer=x-> System.out.println(x);consumer.accept("Hello Consumer");//結果:Hello Consumer}(4)語法格式四:Lambda需要兩個參數,并且Lambda體中有多條語句。
@Testpublic void test04(){Comparator<Integer> com=(x, y)->{System.out.println("函數式接口");return Integer.compare(x,y);};System.out.println(com.compare(2,4));//結果:-1}(5)語法格式五:有兩個以上參數,有返回值,若Lambda體中只有一條語句,return和大括號都可以省略不寫
@Testpublic void test05(){Comparator<Integer> com=(x, y)-> Integer.compare(x,y);System.out.println(com.compare(4,2));//結果:1}(6)Lambda表達式的參數列表的數據類型可以省略不寫,因為JVM可以通過上下文推斷出數據類型,即“類型推斷”
@Testpublic void test06(){Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y);System.out.println(com.compare(4,2));//結果:1}類型推斷:在執行javac編譯程序時,JVM根據程序的上下文推斷出了參數的類型。Lambda表達式依賴于上下文環境。
語法背誦口訣:左右遇一括號省,左側推斷類型省,能省則省。
4. 函數式接口
4.1 什么是函數式接口
==只包含一個抽象方法的接口,就稱為函數式接口。==我們可以通過Lambda表達式來創建該接口的實現對象。
 我們可以在任意函數式接口上使用@FunctionalInterface注解,這樣做可以用于檢測它是否是一個函數式接口,同時javadoc也會包含一條聲明,說明這個接口是一個函數式接口。
4.2 自定義函數式接口
按照函數式接口的定義,自定義一個函數式接口,如下:
@FunctionalInterface public interface MyFuncInterf<T> {public T getValue(String origin); }定義一個方法將函數式接口作為方法參數。
public String toLowerString(MyFuncInterf<String> mf,String origin){return mf.getValue(origin);}將Lambda表達式實現的接口作為參數傳遞。
public void test07(){String value=toLowerString((str)->{return str.toLowerCase();},"ABC");System.out.println(value);//結果ABC}4.3 Java內置函數式接口
四大核心函數式接口的介紹,如圖所示:
 
 使用示例:
 1.Consumer:消費型接口 void accept(T t)
2.Supplier:供給型接口 T get()
/*** 產生指定的整數集合放到集合中* Iterable接口的forEach方法的定義:方法中使用到了Consumer消費型接口,* default void forEach(Consumer<? super T> action) {* Objects.requireNonNull(action);* for (T t : this) {* action.accept(t);* }* }*/@Testpublic void test02(){List list = addNumInList(10, () -> (int) (Math.random() * 100));list.forEach(t-> System.out.println(t));}public List addNumInList(int size, Supplier<Integer> supplier){List<Integer> list=new ArrayList();for (int i = 0; i < size; i++) {list.add(supplier.get());}return list;}3.Function<T,R>:函數型接口 R apply(T t)
/*** * 使用函數式接口處理字符串。*/public String handleStr(String s,Function<String,String> f){return f.apply(s);}@Testpublic void test03(){System.out.println(handleStr("abc",(String s)->s.toUpperCase()));}//結果:ABC4.Predicate:斷言型接口 boolean test(T t)
/*** 自定義條件過濾字符串集合*/@Testpublic void test04(){List<String> strings = Arrays.asList("啊啊啊", "2333", "666", "?????????");List<String> stringList = filterStr(strings, (s) -> s.length() > 3);for (String s : stringList) {System.out.println(s);}}public List<String> filterStr(List<String> list, Predicate<String> predicate){ArrayList result = new ArrayList();for (int i = 0; i < list.size(); i++) {if (predicate.test(list.get(i))){result.add(list.get(i));}}return result;}其他接口的定義,如圖所示:
 
5. 方法引用
當要傳遞給Lambda體的操作,已經有實現的方法了,就可以使用方法引用!(實現抽象方法的參數列表,必須與方法引用的參數列表一致,方法的返回值也必須一致,即方法的簽名一致)。方法引用可以理解為方法引用是Lambda表達式的另外一種表現形式。
 方法引用的語法:使用操作符“::”將對象或類和方法名分隔開。
 方法引用的使用情況共分為以下三種:
- 對象::實例方法名
- 類::靜態方法名
- 類::實例方法名
使用示例:
 1.對象::實例方法名
3.類::實例方法名
@Testpublic void test3(){BiPredicate<String,String> bp=(x,y)->x.equals(y);//使用方法引用實現相同效果BiPredicate<String,String> bp2=String::equals;System.out.println(bp.test("1","2"));//結果:falseSystem.out.println(bp.test("1","2"));//結果:false}6. 構造器引用
格式:類名::new
 與函數式接口相結合,自動與函數式接口中方法兼容,可以把構造器引用賦值給定義的方法。需要注意構造器參數列表要與接口中抽象方法的參數列表一致。使用示例:
 創建一個實體類Employee:
使用構造器引用與函數式接口相結合
@Testpublic void test01(){//引用無參構造器Supplier<Employee> supplier=Employee::new;System.out.println(supplier.get());//引用有參構造器Function<Integer,Employee> function=Employee::new;System.out.println(function.apply(21));BiFunction<Integer,Integer,Employee> biFunction=Employee::new;System.out.println(biFunction.apply(8,24));}輸出結果:Employee{id=null, name='null', age=null}Employee{id=21, name='null', age=null}Employee{id=8, name='null', age=24}7. 數組引用
數組引用的格式:type[]:new
 使用示例:
8. Lambda表達式的作用域
Lambda表達式可以看作是匿名內部類實例化的對象,Lambda表達式對變量的訪問限制和匿名內部類一樣,因此Lambda表達式可以訪問局部變量、局部引用,靜態變量,實例變量。
8.1 訪問局部變量
在Lambda表達式中規定只能引用標記了final的外層局部變量。我們不能在lambda 內部修改定義在域外的局部變量,否則會編譯錯誤。
public class TestFinalVariable {interface VarTestInterface{Integer change(String str);}public static void main(String[] args) {//局部變量不使用final修飾Integer tempInt = 1;VarTestInterface var = (str -> Integer.valueOf(str+tempInt));//再次修改,不符合隱式final定義tempInt =2;Integer str =var.change("111") ;System.out.println(str);} }上面代碼會出現編譯錯誤,出現如下提示:
 
特殊情況下,局部變量也可以不用聲明為 final,但是必須不可被后面的代碼修改(即隱性的具有 final 的語義)
例如上面的代碼確保Lambda表達式后局部變量后面不做修改,就可以成功啦!
public class TestFinalVariable {interface VarTestInterface{Integer change(String str);}public static void main(String[] args) {//局部變量不使用final修飾Integer tempInt = 1;VarTestInterface var = (str -> Integer.valueOf(str+tempInt));Integer str =var.change("111") ;System.out.println(str);} }8.2 訪問局部引用,靜態變量,實例變量
Lambda表達式不限制訪問局部引用變量,靜態變量,實例變量。代碼測試都可正常執行,代碼:
public class LambdaScopeTest {/*** 靜態變量*/private static String staticVar;/*** 實例變量*/private static String instanceVar;@FunctionalInterfaceinterface VarChangeInterface{Integer change(String str);}/*** 測試引用變量*/private void testReferenceVar(){ArrayList<String> list = new ArrayList<>();list.add("111");//訪問外部引用局部引用變量VarChangeInterface varChangeInterface = ((str) -> Integer.valueOf(list.get(0)));//修改局部引用變量list.set(0,"222");Integer str =varChangeInterface.change("");System.out.println(str);}/*** 測試靜態變量*/void testStaticVar(){staticVar="222";VarChangeInterface varChangeInterface = (str -> Integer.valueOf(str+staticVar));staticVar="333";Integer str =varChangeInterface.change("111") ;System.out.println(str);}/*** 測試實例變量*/void testInstanceVar(){instanceVar="222";VarChangeInterface varChangeInterface = (str -> Integer.valueOf(str+instanceVar));instanceVar="333";Integer str =varChangeInterface.change("111") ;System.out.println(str);}public static void main(String[] args) {new LambdaScopeTest().testReferenceVar();new LambdaScopeTest().testStaticVar();new LambdaScopeTest().testInstanceVar();} }Lambda表達式里不允許聲明一個與局部變量同名的參數或者局部變量。
//編程報錯Integer tempInt = 1;VarTestInterface varTest01 = (tempInt -> Integer.valueOf(tempInt));VarTestInterface varTest02 = (str -> {Integer tempInt = 1;Integer.valueOf(str);});8.3 Lambda表達式訪問局部變量作限制的原因
Lambda表達式不能訪問非final修飾的局部變量的原因是,局部變量是保存在棧幀中的。而在Java的線程模型中,棧幀中的局部變量是線程私有的,如果允許Lambda表達式訪問到棧幀中的變量地址(可改變的局部變量),則會可能導致線程私有的數據被并發訪問,造成線程不安全問題。
基于上述,對于引用類型的局部變量,因為Java是值傳遞,又因為引用類型的指向內容是保存在堆中,是線程共享的,因此Lambda表達式中可以修改引用類型的局部變量的內容,而不能修改該變量的引用。
對于基本數據類型的變量,在 Lambda表達式中只是獲取到該變量的副本,且局部變量是線程私有的,因此無法知道其他線程對該變量的修改,如果該變量不做final修飾,會造成數據不同步的問題。
但是實例變量,靜態變量不作限制,因為實例變量,靜態變量是保存在堆中(Java8之后),而堆是線程共享的。在Lambda表達式內部是可以知道實例變量,靜態變量的變化。
9. Lambda表達式的優缺點
- 優點:
有利于JIT編譯器對代碼進行優化
- 缺點:
在Stream操作中使用lambda表達式:Java8新特性Stream的使用總結
筆記總結自:尚硅谷的視頻教程-【Java8新特性】
 參考:
 1.Java 8 Lambda 表達式
 2.Lambda-讓人又愛又恨的“->"
 推薦閱讀:聊聊Java 8 Lambda 表達式
總結
以上是生活随笔為你收集整理的Lambda表达式超详细总结的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 转:张五常:比知识更重要的,是思维方式
- 下一篇: 华景机器人百度_华景机器人表情符整理
