第一次接觸lambda表達式時,感受這個東西挺神奇的(高逼格),一個()加->就能傳遞一段代碼,當時公司項目中接手同事的代碼,本身也對java8的特性不瞭解,看的也是一頭霧水,以後就趕快看了下《java8實戰》這本書,決定寫一個java8特性系列的博客,既加深本身的印象,還能跟你們分享一下,但願你們多多指教😄。java
Lambda是一個匿名函數,咱們能夠把Lambda表達式理解爲是一段能夠傳遞的代碼(將代碼像參數同樣進行傳遞,稱爲行爲參數化)。Lambda容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中),要作到這一點就須要瞭解,什麼是函數式接口,這裏先不作介紹,等下一篇在講解。bash
首先先看一下lambda長什麼樣? 正常寫法:ide
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
複製代碼
lambda寫法:函數
new Thread(
() -> System.out.println("hello lambda")
).start();
複製代碼
怎麼樣?是否是感受很簡潔,沒錯,這就是lambda的魅力,他可讓你寫出來的代碼更簡單、更靈活。工具
可選類型聲明:
不須要聲明參數類型,編譯器能夠統一識別參數值。也就說(s) -> System.out.println(s)和 (String s) -> System.out.println(s)是同樣的編譯器會進行類型推斷因此不須要添加參數類型。可選的參數圓括號:
一個參數無需定義圓括號,但多個參數須要定義圓括號。例如:可選的大括號:
若是主體包含了一個語句,就不須要使用大括號。
可選的返回關鍵字:
若是主體只有一個表達式返回值則編譯器會自動返回值,大括號須要指定明表達式返回了一個數值。Lambda體不加{ }就不用寫return:ui
Comparator<Integer> com = (x, y) -> Integer.compare(y, x);
複製代碼
Lambda體加上{ }就須要添加return:spa
Comparator<Integer> com = (x, y) -> {
int compare = Integer.compare(y, x);
return compare;
};
複製代碼
上面咱們看到了一個lambda表達式應該怎麼寫,但lambda中有一個重要特徵是可選參數類型聲明
,就是說不用寫參數的類型,那麼爲何不用寫呢?它是怎麼知道的參數類型呢?這就涉及到類型推斷了。code
java8的泛型類型推斷改進:cdn
List<Person> ps = ...
Stream<String> names = ps.stream().map(p -> p.getName());
複製代碼
在上面的代碼中,ps的類型是List<Person>
,因此ps.stream()的返回類型是Stream<Person>
。map()方法接收一個類型爲Function<T, R>的函數式接口,這裏T的類型便是Stream元素的類型,也就是Person,而R的類型未知。因爲在重載解析以後lambda表達式的目標類型仍然未知,咱們就須要推導R的類型:經過對lambda表達式lambda進行類型檢查,咱們發現lambda體返回String,所以R的類型是String,於是map()返回Stream<String>
。絕大多數狀況下編譯器都能解析出正確的類型,但若是碰到沒法解析的狀況,咱們則須要:對象
<String>
map(p -> p.getName()))方法引用是用來直接訪問類或者實例已經存在的方法或構造方法,提供了一種引用而不執行方法的方式。是一種更簡潔更易懂的Lambda表達式,當Lambda表達式中只是執行一個方法調用時,直接使用方法引用的形式可讀性更高一些。 方法引用使用 「 :: 」 操做符來表示,左邊是類名或實例名,右邊是方法名。 (注意:方法引用::右邊的方法名是不須要加()的,例:User::getName)
方法引用的幾種形式:
例如:
Consumer<String> consumer = (s) -> System.out.println(s);
等同於:
Consumer<String> consumer = System.out::println;
例如:
Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);
等同於:
Function<String, Integer> stringToInteger = Integer::parseInt;
例如:
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);
等同於:
BiPredicate<List<String>, String> contains = List::contains;
複製代碼
注意:
語法格式:類名::new
例如:
Supplier<User> supplier = ()->new User();
等同於:
Supplier<User> supplier = User::new;
複製代碼
注意:
須要調用的構造器方法與函數式接口中抽象方法的參數列表保持一致。
研究了半天Lambda怎麼寫,但是它的原理是什麼?咱們簡單看個例子,看看真相究竟是什麼:
public class StreamTest {
public static void main(String[] args) {
printString("hello lambda", (String s) -> System.out.println(s));
}
public static void printString(String s, Print<String> print) {
print.print(s);
}
}
@FunctionalInterface
interface Print<T> {
public void print(T t);
}
複製代碼
上面的代碼自定義了一個函數式接口,定義一個靜態方法而後用這個函數式接口來接收參數。編寫完這個類之後,咱們到終端界面javac進行編譯,而後用javap(javap是jdk自帶的反解析工具。它的做用就是根據class字節碼文件,反解析出當前類對應的code區(彙編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。)進行解析,以下圖:
lambda$main$0
靜態方法,這個靜態方法實現了Lambda表達式的邏輯,如今咱們知道原來Lambda表達式被編譯成了一個靜態方法,那麼這個靜態方式是怎麼調用的呢?咱們繼續進行public com.lxs.stream.StreamTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #2 // String hello lambda
2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
7: invokestatic #4 // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V
10: return
LineNumberTable:
line 10: 0
line 12: 10
public static void printString(java.lang.String, com.lxs.stream.Print<java.lang.String>);
descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: aload_1
1: aload_0
2: invokeinterface #5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V
7: return
LineNumberTable:
line 15: 0
line 16: 7
Signature: #19 // (Ljava/lang/String;Lcom/lxs/stream/Print<Ljava/lang/String;>;)V
private static void lambda$main$0(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 10: 0
}
SourceFile: "StreamTest.java"
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;)V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
#30 (Ljava/lang/String;)V
複製代碼
這裏只貼出了一部分的字節碼結構,因爲常量池定義太長了,就沒有粘貼。
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;)V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
#30 (Ljava/lang/String;)V
複製代碼
經過這段字節碼結構發現是要生成一個內部類,使用invokestatic調用了一個LambdaMetafactory.metafactory方法,並把lambda$main$0
做爲參數傳了進去,咱們來看metafactory 的方法裏的實現代碼:
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
複製代碼
在buildCallSite的函數中,是函數spinInnerClass 構建了這個內部類。也就是生成了一個StreamTest$$Lambda$1.class這樣的內部類,這個類是在運行的時候構建的,並不會保存在磁盤中。
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
如下省略。。。
}
複製代碼
若是想看到這個構建的類,能夠經過設置環境參數 System.setProperty("jdk.internal.lambda.dumpProxyClasses", " . "); 會在你指定的路徑 . 當前運行路徑上生成這個內部類。咱們看下一下生成的類長什麼樣
咱們在javap -v -p StreamTest$$Lambda$1.class看下:
{
private com.lxs.stream.StreamTest$$Lambda$1();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void print(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: checkcast #15 // class java/lang/String
4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
7: return
RuntimeVisibleAnnotations:
0: #13()
}
複製代碼
發如今重寫的parint方法中使用invokestatic指令調用了lambda$main$0方法。
總結: 這樣實現了Lambda表達式,使用invokedynamic指令,運行時調用LambdaMetafactory.metafactory動態的生成內部類,實現了函數式接口,並在重寫函數式接口中的方法,在方法內調用lambda$main$0
,內部類裏的調用方法塊並非動態生成的,只是在原class裏已經編譯生成了一個靜態的方法,內部類只須要調用該靜態方法。
你們看后辛苦點個贊點關注哦!後續還會後更多的博客。 若有錯誤,煩請指正。