1、什麼是lambda表達式?git
Lambda 是一個匿名函數,咱們能夠把 Lambda 表達式理解爲是一段能夠傳遞的代碼(將代碼像數據同樣進行傳遞)。能夠寫出更簡潔、更靈活的代碼。做爲一種更緊湊的代碼風格,使 Java的語言表達能力獲得了提高。redis
匿名內部類的寫法:express
public void demo1(){ Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; Runnable runnable = new Runnable() { @Override public void run() {} }; }
這樣寫會發現一個問題,實現的方法是冗餘的代碼,實際當中並無什麼用處。咱們看看Lambda的寫法。微信
Lambda表達式的寫法多線程
public void demo2(){ Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y); Runnable runnable = () -> System.out.println("lambda表達式"); }
咱們會發現Lambda表達式的寫法更加的簡潔、靈活。它只關心參數和執行的功能(具體須要幹什麼,好比->後的Integer.compare(x, y))。app
2、lambda表達式語法ide
lambda表達式的通常語法:函數
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
包含三個部分:參數列表,箭頭(->),以及一個表達式或語句塊。
1.一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數
2.一個箭頭符號:->
3.方法體,能夠是表達式和代碼塊,方法體是函數式接口裏面方法的實現,若是是代碼塊,則必須用{}來包裹起來,且須要一個return 返回值,但有個例外,若函數式接口裏面方法返回值是void,則無需{}。
整體看起來像這樣:
(parameters) -> expression 或者 (parameters) -> { statements; }
上面的lambda表達式語法能夠認爲是最全的版本,寫起來仍是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各類簡化版:
1. 參數類型省略–絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM; }
2. 單參數語法:當lambda表達式的參數個數只有一個,能夠省略小括號。lambda表達式簡寫爲:
param1 -> { statment1; statment2; //............. return statmentM; }
3. 單語句寫法:當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號。lambda表達式簡化爲:
param1 -> statment
下面看幾個例子:
demo1:無參,無返回值,Lambda 體只需一條語句
Runnable runnable = () -> System.out.println("lamda表達式");
demo2:Lambda 只須要一個參數
Consumer<String> consumer=(x)->System.out.println(x);
demo3:Lambda 只須要一個參數時,參數的小括號能夠省略
Consumer<String> consumer=x->System.out.println(x);
demo4:Lambda 須要兩個參數
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
demo5:當 Lambda 體只有一條語句時,return 與大括號能夠省略
BinaryOperator<Integer> binaryOperator=(x,y)->(x+y);
demo6:數據類型能夠省略,由於可由編譯器推斷得出,稱爲「類型推斷」
BinaryOperator<Integer> bo=(x,y)->{ System.out.println("Lambda"); return x+y;};
類型推斷
上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然能夠編譯,這是由於 javac 根據程序的上下文,在後臺推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的 「類型推斷」。
3、lambda表達式的類型
咱們都知道,Java是一種強類型語言。全部的方法參數都有類型,那麼lambda表達式是一種什麼類型呢?
View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { //... } }; button.setOnClickListener(listener);
如上所示,以往咱們是經過使用單一方法的接口來表明一個方法而且重用它。
在lambda表達式中,仍使用的和以前同樣的形式。咱們叫作函數式接口(functional interface)。如咱們以前button的點擊響應事件使用的View.OnClickListener
就是一個函數式接口。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... public interface OnClickListener { void onClick(View v); } ... }
那究竟什麼樣的接口是函數式接口呢?
函數式接口是隻有一個抽象方法的接口,用做表示lambda表達式的類型。 好比Java標準庫中的java.lang.Runnable和java.util.Comparator都是典型的函數式接口。java 8提供 @FunctionalInterface做爲註解,這個註解是非必須的,只要接口符合函數式接口的標準(即只包含一個方法的接口),虛擬機會自動判斷,但最好在接口上使用註解@FunctionalInterface進行聲明,以避免團隊的其餘人員錯誤地往接口中添加新的方法。舉例以下:
@FunctionalInterface public interface Runnable { void run(); } public interface Callable<V> { V call() throws Exception; } public interface ActionListener { void actionPerformed(ActionEvent e); } public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
注意最後這個Comparator接口。它裏面聲明瞭兩個方法,貌似不符合函數接口的定義,但它的確是函數接口。這是由於equals方法是Object的,全部的接口都會聲明Object的public方法——雖然大可能是隱式的。因此,Comparator顯式的聲明瞭equals不影響它依然是個函數接口。
Java中的lambda沒法單獨出現,它須要一個函數式接口來盛放,lambda表達式方法體其實就是函數接口的實現。即Lambda表達式不能脫離目標類型存在,這個目標類型就是函數式接口,看下面的例子:
String []datas = new String[] {"peng","zhao","li"}; Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length()); Arrays.sort(datas,comp); Stream.of(datas).forEach(param -> {System.out.println(param);});
Lambda表達式被賦值給了comp函數接口變量。
你能夠用一個lambda表達式爲一個函數接口賦值:
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
而後再賦值給一個Object:
Object obj = r1;
但卻不能這樣幹:
Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!
必須顯式的轉型成一個函數接口才能夠:
Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
一個lambda表達式只有在轉型成一個函數接口後才能被當作Object使用。因此下面這句也不能編譯:
System.out.println( () -> {} ); //錯誤! 目標類型不明
必須先轉型:
System.out.println( (Runnable)() -> {} ); // 正確
假設你本身寫了一個函數接口,長的跟Runnable如出一轍:
@FunctionalInterface public interface MyRunnable { public void run(); }
那麼
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};
都是正確的寫法。這說明一個lambda表達式能夠有多個目標類型(函數接口),只要函數匹配成功便可。但需注意一個lambda表達式必須至少有一個目標類型。
JDK預約義了不少函數接口以免用戶重複定義。最典型的是Function:
@FunctionalInterface public interface Function<T, R> { R apply(T t); }
這個接口表明一個函數,接受一個T類型的參數,並返回一個R類型的返回值。另外一個預約義函數接口叫作Consumer,跟Function的惟一不一樣是它沒有返回值。
@FunctionalInterface public interface Consumer<T> { void accept(T t); }
還有一個Predicate,用來判斷某項條件是否知足。常常用來進行篩濾操做:
@FunctionalInterface public interface Predicate<T> { boolean test(T t); }
綜上所述,一個lambda表達式其實就是定義了一個匿名方法,只不過這個方法必須符合至少一個函數接口。
4、lambda表達式可以使用的變量
先舉例:
@Test public void test1(){ //將爲列表中的字符串添加前綴字符串 String waibu = "lambda :"; List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"}); List<String>execStrs = proStrs.stream().map(chuandi -> { Long zidingyi = System.currentTimeMillis(); return waibu + chuandi + " -----:" + zidingyi; }).collect(Collectors.toList()); execStrs.forEach(System.out::println); }
輸出:
lambda :Ni -----:1498722594781 lambda :Hao -----:1498722594781 lambda :Lambda -----:1498722594781
變量waibu :外部變量
變量chuandi :傳遞變量
變量zidingyi :內部自定義變量
lambda表達式能夠訪問給它傳遞的變量,訪問本身內部定義的變量,同時也能訪問它外部的變量。不過lambda表達式訪問外部變量有一個很是重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。
當在表達式內部修改waibu = waibu + " ";時,IDE就會提示你:
Local variable waibu defined in an enclosing scope must be final or effectively final
編譯時會報錯。由於變量waibu被lambda表達式引用,因此編譯器會隱式的把其當成final來處理。
之前Java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。如今java8對這個限制作了優化,能夠不用顯示使用final修飾,可是編譯器隱式當成final來處理。
5、lambda表達式做用域
整體來講,Lambda表達式的變量做用域與內部類很是類似,只是條件相對來講,放寬了些,之前內部類要想引用外部類的變量,必須像下面這樣
final String[] datas = new String[] { "peng", "Zhao", "li" }; new Thread(new Runnable() { @Override public void run() { System.out.println(datas); } }).start();
將變量聲明爲final類型的,如今在Java 8中能夠這樣寫代碼
String []datas = new String[] {"peng","Zhao","li"}; new Thread(new Runnable() { @Override public void run() { System.out.println(datas); } }).start();
也能夠這樣寫:
new Thread(() -> System.out.println(datas)).start();
看了上面的兩段代碼,可以發現一個顯著的不一樣,就是Java 8中內部類或者Lambda表達式對外部類變量的引用條件放鬆了,不要求強制的加上final關鍵字了,可是Java 8中要求這個變量是effectively final。What is effectively final?
Effectively final就是有效只讀變量,意思是這個變量能夠不加final關鍵字,可是這個變量必須是隻讀變量,即一旦定義後,在後面就不能再隨意修改,以下代碼會編譯出錯
String []datas = new String[] {"peng","Zhao","li"}; datas = null; new Thread(() -> System.out.println(datas)).start();
Java中內部類以及Lambda表達式中也不容許修改外部類中的變量,這是爲了不多線程狀況下的race condition。
6、lambda表達式中的this概念
在lambda中,this不是指向lambda表達式產生的那個對象,而是聲明它的外部對象。
例如:
package com.demo; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class WhatThis { public void whatThis(){ //轉全小寫 List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"}); List<String> execStrs = proStrs.stream().map(str -> { System.out.println(this.getClass().getName()); return str.toLowerCase(); }).collect(Collectors.toList()); execStrs.forEach(System.out::println); } public static void main(String[] args) { WhatThis wt = new WhatThis(); wt.whatThis(); } }
輸出:
com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda