JDK8中包含了許多內建的Java中經常使用到函數接口,好比Comparator或者Runnable接口,這些接口都增長了@FunctionalInterface註解以便能用在lambda上。java
name
|
type
|
description
|
Consumer
|
Consumer< T >
|
接收T對象,不返回值
|
Predicate
|
Predicate< T >
|
接收T對象並返回boolean
|
Function
|
Function< T, R >
|
接收T對象,返回R對象
|
Supplier
|
Supplier< T >
|
提供T對象(例如工廠),不接收值
|
UnaryOperator
|
UnaryOperator
|
接收T對象,返回T對象
|
BinaryOperator
|
BinaryOperator
|
接收兩個T對象,返回T對象
|
標註爲@FunctionalInterface的接口是函數式接口,該接口只有一個自定義方法。注意,只要接口只包含一個抽象方法,編譯器就默認該接口爲函數式接口。lambda表示是一個命名方法,將行爲像數據同樣進行傳遞。git
該方法的簽名爲void forEach(Consumer<? super E> action),做用是對容器中的每一個元素執行action指定的動做,其中Consumer是個函數接口,裏面只有一個待實現方法void accept(T t)。注意,這裏的Consumer不重要,只須要知道它是一個函數式接口便可,通常使用不會看見Consumer的身影。github
list.forEach(item -> System.out.println(item));
該方法簽名爲boolean removeIf(Predicate<? super E> filter),做用是刪除容器中全部知足filter指定條件的元素,其中Predicate是一個函數接口,裏面只有一個待實現方法boolean test(T t),一樣的這個方法的名字根本不重要,由於用的時候不須要書寫這個名字。spring
// list中元素類型String list.removeIf(item -> item.length() < 2);
List.replaceAll()編程
該方法簽名爲void replaceAll(UnaryOperator<E> operator),做用是對每一個元素執行operator指定的操做,並用操做結果來替換原來的元素。數組
// list中元素類型String list.replaceAll(item -> item.toUpperCase());
List.sort()數據結構
該方法定義在List接口中,方法簽名爲void sort(Comparator<? super E> c),該方法根據c指定的比較規則對容器元素進行排序。Comparator接口咱們並不陌生,其中有一個方法int compare(T o1, T o2)須要實現,顯然該接口是個函數接口。app
// List.sort()方法結合Lambda表達式 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.sort((str1, str2) -> str1.length()-str2.length());
Map.forEach()less
該方法簽名爲void forEach(BiConsumer<? super K,? super V> action),做用是對Map中的每一個映射執行action指定的操做,其中BiConsumer是一個函數接口,裏面有一個待實現方法void accept(T t, U u)。ide
map.forEach((key, value) -> System.out.println(key + ": " + value));
認識了幾個Java8 Collection新增的幾個方法,在瞭解下Stream API,你會發現它在集合數據處理方面的強大做用。常見的Stream接口繼承關係圖以下:
對Stream的操做分爲2種,中間操做與結束操做,兩者的區別是,前者是惰性執行,調用中間操做只會生成一個標記了該操做的新的stream而已;後者會把全部中間操做積攢的操做以pipeline的方式執行,這樣能夠減小迭代次數。計算完成以後stream就會失效。
操做類型
|
接口方法
|
中間操做
|
concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered()
|
結束操做
|
allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()
|
stream方法
forEach()
stream的遍歷操做。
filter()
函數原型爲Stream<T> filter(Predicate<? super T> predicate),做用是返回一個只包含知足predicate條件元素的Stream。
distinct()
函數原型爲Stream<T> distinct(),做用是返回一個去除重複元素以後的Stream。
sorted()
排序函數有兩個,一個是用天然順序排序,一個是使用自定義比較器排序,函數原型分別爲Stream<T> sorted()和Stream<T> sorted(Comparator<? super T> comparator)。
map()
函數原型爲<R> Stream<R> map(Function<? super T,? extends R> mapper),做用是返回一個對當前全部元素執行執行mapper以後的結果組成的Stream。直觀的說,就是對每一個元素按照某種操做進行轉換,轉換先後Stream中元素的個數不會改變,但元素的類型取決於轉換以後的類型。
List<Integer> list = CollectionUtil.newArrayList(1, 2, 3, 4); list.stream().map(item -> String.valueOf(item)).forEach(System.out::println); flapmap()
和map相似,不一樣的是每一個元素轉換獲得的是stream對象,會把子stream對象壓縮到父集合中。
List<List<String>> list3 = Arrays.asList( Arrays.asList("aaa", "bb", "ccc"), Arrays.asList("aa", "bbb", "ccc")); list3.stream().flatMap(Collection::stream).collect(Collectors.toList());
reduce 和 collect
reduce的做用是從stream中生成一個值,sum()、max()、min()、count()等都是reduce操做,將他們單獨設爲函數只是由於經常使用。
// 找出最長的單詞 Stream<String> stream = Stream.of("I", "love", "you", "too"); Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
collect方法是stream中重要的方法,若是某個功能沒有在Stream接口中找到,則能夠經過collect方法實現。
// 將Stream轉換成容器或Map Stream<String> stream = Stream.of("I", "love", "you", "too"); List<String> list = stream.collect(Collectors.toList()); // Set<String> set = stream.collect(Collectors.toSet()); // Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
諸如String::length的語法形式稱爲方法引用,這種語法用來替代某些特定形式Lambda表達式。若是Lambda表達式的所有內容就是調用一個已有的方法,那麼能夠用方法引用來替代Lambda表達式。方法引用能夠細分爲四類。引用靜態方法 Integer::sum,引用某個對象的方法 list::add,引用某個類的方法 String::length,引用構造方法 HashMap::new。
ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you"); list.stream() .filter(s -> s.length() > 1) .map(String::toUpperCase) .sorted() .forEach(System.out::println);
上面的代碼和下面的功能同樣,不過下面的代碼便於打斷點調試。
ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you"); list.stream() .filter(s -> { return s.length() > 1; }) .map(s -> { return s.toUpperCase(); }) .sorted() .forEach(s -> { System.out.println(s); });
首先filter方法瞭解一下:
// ReferencePipeline @Override public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { Objects.requireNonNull(predicate); return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { // 生成state對應的Sink實現 @Override Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) { return new Sink.ChainedReference<P_OUT, P_OUT>(sink) { @Override public void begin(long size) { downstream.begin(-1); } @Override public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); } }; } }; }
filter方法返回一個StatelessOp實例,並實現了其opWrapSink方法,能夠確定的是opWrapSink方法在以後某個時間點會被調用,進行Sink實例的建立。從代碼中能夠看出,filter方法不會進行真正的filter動做(也就是遍歷列表進行filter操做)。
filter方法中出現了2個新面孔,StatelessOp和Sink,既然是新面孔,那就先認識下:
abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>> extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S>
StatelessOp繼承自AbstractPipeline,lambda的流處理能夠分爲多個stage,每一個stage對應一個AbstractPileline和一個Sink。
Stream流水線組織結構示意圖以下:
圖中經過Collection.stream()方法獲得Head也就是stage0,緊接着調用一系列的中間操做,不斷產生新的Stream。這些Stream對象以雙向鏈表的形式組織在一塊兒,構成整個流水線,因爲每一個Stage都記錄了前一個Stage和本次的操做以及回調函數,依靠這種結構就能創建起對數據源的全部操做。這就是Stream記錄操做的方式。
Stream上的全部操做分爲兩類:中間操做和結束操做,中間操做只是一種標記,只有結束操做纔會觸發實際計算。中間操做又能夠分爲無狀態的(Stateless)和有狀態的(Stateful),無狀態中間操做是指元素的處理不受前面元素的影響,而有狀態的中間操做必須等到全部元素處理以後才知道最終結果,好比排序是有狀態操做,在讀取全部元素以前並不能肯定排序結果。
有了AbstractPileline,就能夠把整個stream上的多個處理操做(filter/map/...)串起來,可是這隻解決了多個處理操做記錄的問題,還須要一種將全部操做疊加到一塊兒的方案。你可能會以爲這很簡單,只須要從流水線的head開始依次執行每一步的操做(包括回調函數)就好了。這聽起來彷佛是可行的,可是你忽略了前面的Stage並不知道後面Stage到底執行了哪一種操做,以及回調函數是哪一種形式。換句話說,只有當前Stage自己才知道該如何執行本身包含的動做。這就須要有某種協議來協調相鄰Stage之間的調用關係。這就須要Sink接口了,Sink包含的方法以下:
方法名
|
做用
|
void begin(long size)
|
開始遍歷元素以前調用該方法,通知Sink作好準備。
|
void end()
|
全部元素遍歷完成以後調用,通知Sink沒有更多的元素了。
|
boolean cancellationRequested()
|
是否能夠結束操做,可讓短路操做盡早結束。
|
void accept(T t)
|
遍歷元素時調用,接受一個待處理元素,並對元素進行處理。Stage把本身包含的操做和回調方法封裝到該方法裏,前一個Stage只須要調用當前Stage.accept(T t)方法就好了。
|
有了上面的協議,相鄰Stage之間調用就很方便了,每一個Stage都會將本身的操做封裝到一個Sink裏,前一個Stage只需調用後一個Stage的accept()方法便可,並不須要知道其內部是如何處理的。固然對於有狀態的操做,Sink的begin()和end()方法也是必須實現的。好比Stream.sorted()是一個有狀態的中間操做,其對應的Sink.begin()方法可能建立一個存放結果的容器,而accept()方法負責將元素添加到該容器,最後end()負責對容器進行排序。Sink的四個接口方法經常相互協做,共同完成計算任務。實際上Stream API內部實現的的本質,就是如何重載Sink的這四個接口方法。
回到最開始地方的代碼示例,map/sorted方法流程大體和filter相似,這些操做都是中間操做。重點關注下forEach方法:
// ReferencePipeline @Override public void forEach(Consumer<? super P_OUT> action) { evaluate(ForEachOps.makeRef(action, false)); } // ... -> // AbstractPipeline @Override final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) { copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator); return sink; } @Override final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) { // 各個pipeline的opWrapSink方法回調 for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) { sink = p.opWrapSink(p.previousStage.combinedFlags, sink); } return (Sink<P_IN>) sink; } @Override final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) { if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) { // sink各個方法的回調 wrappedSink.begin(spliterator.getExactSizeIfKnown()); spliterator.forEachRemaining(wrappedSink); wrappedSink.end(); } else { copyIntoWithCancel(wrappedSink, spliterator); } }
forEach()流程中會觸發各個Sink的操做,也就是執行各個lambda表達式裏的邏輯了。到這裏整個lambda流程也就完成了。
Java lambda 一眼看上去有點像匿名內部類的簡化形式,可是兩者確有着本質的差異。匿名內部類經編譯後會生成對應的class文件,格式爲XXX$n.class;而lambda代碼通過編譯後生成一個private方法,方法名格式爲lambda$main$n。
// Application.main 方法中代碼 ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you"); list.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); list.forEach(System.out::println);
以上代碼就會產生一個Application$1.class文件和一個lambda$main$0的方法。既然lambda實現不是內部類,那麼在lambda中this就表明的當前所在類實例。
// Application.main 方法中代碼 ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you"); list.forEach(item -> { System.out.println(item); });
經過javap -c -p Application.class查看以上代碼對應的字節碼:
Constant pool: #1 = Methodref #12.#36 // java/lang/Object."<init>":()V #2 = Class #37 // java/lang/String #3 = String #38 // I #4 = String #39 // love #5 = String #40 // you #6 = Methodref #41.#42 // cn/hutool/core/collection/CollectionUtil.newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList; #7 = InvokeDynamic #0:#48 // #0:accept:()Ljava/util/function/Consumer; #8 = Methodref #49.#50 // java/util/ArrayList.forEach:(Ljava/util/function/Consumer;)V #9 = Fieldref #51.#52 // java/lang/System.out:Ljava/io/PrintStream; #10 = Methodref #53.#54 // java/io/PrintStream.println:(Ljava/lang/String;)V #11 = Class #55 // com/luo/demo/Application #12 = Class #56 // java/lang/Object #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/luo/demo/Application; #20 = Utf8 main #21 = Utf8 ([Ljava/lang/String;)V #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 list #25 = Utf8 Ljava/util/ArrayList; #26 = Utf8 LocalVariableTypeTable #27 = Utf8 Ljava/util/ArrayList<Ljava/lang/String;>; #28 = Utf8 lambda$main$0 #29 = Utf8 (Ljava/lang/String;)V #30 = Utf8 item #31 = Utf8 Ljava/lang/String; #32 = Utf8 SourceFile #33 = Utf8 Application.java #34 = Utf8 RuntimeVisibleAnnotations #35 = Utf8 Lorg/springframework/boot/autoconfigure/SpringBootApplication; #36 = NameAndType #13:#14 // "<init>":()V #37 = Utf8 java/lang/String #38 = Utf8 I #39 = Utf8 love #40 = Utf8 you #41 = Class #57 // cn/hutool/core/collection/CollectionUtil #42 = NameAndType #58:#59 // newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList; #43 = Utf8 BootstrapMethods #44 = MethodHandle #6:#60 // 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; #45 = MethodType #61 // (Ljava/lang/Object;)V #46 = MethodHandle #6:#62 // invokestatic com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V #47 = MethodType #29 // (Ljava/lang/String;)V #48 = NameAndType #63:#64 // accept:()Ljava/util/function/Consumer; #49 = Class #65 // java/util/ArrayList #50 = NameAndType #66:#67 // forEach:(Ljava/util/function/Consumer;)V #51 = Class #68 // java/lang/System #52 = NameAndType #69:#70 // out:Ljava/io/PrintStream; #53 = Class #71 // java/io/PrintStream #54 = NameAndType #72:#29 // println:(Ljava/lang/String;)V #55 = Utf8 com/luo/demo/Application #56 = Utf8 java/lang/Object #57 = Utf8 cn/hutool/core/collection/CollectionUtil #58 = Utf8 newArrayList #59 = Utf8 ([Ljava/lang/Object;)Ljava/util/ArrayList; #60 = Methodref #73.#74 // 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; #61 = Utf8 (Ljava/lang/Object;)V #62 = Methodref #11.#75 // com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V #63 = Utf8 accept #64 = Utf8 ()Ljava/util/function/Consumer; #65 = Utf8 java/util/ArrayList #66 = Utf8 forEach #67 = Utf8 (Ljava/util/function/Consumer;)V #68 = Utf8 java/lang/System #69 = Utf8 out #70 = Utf8 Ljava/io/PrintStream; #71 = Utf8 java/io/PrintStream #72 = Utf8 println #73 = Class #76 // java/lang/invoke/LambdaMetafactory #74 = NameAndType #77:#81 // 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; #75 = NameAndType #28:#29 // lambda$main$0:(Ljava/lang/String;)V #76 = Utf8 java/lang/invoke/LambdaMetafactory #77 = Utf8 metafactory #78 = Class #83 // java/lang/invoke/MethodHandles$Lookup #79 = Utf8 Lookup #80 = Utf8 InnerClasses #81 = Utf8 (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; #82 = Class #84 // java/lang/invoke/MethodHandles #83 = Utf8 java/lang/invoke/MethodHandles$Lookup #84 = Utf8 java/lang/invoke/MethodHandles { public com.luo.demo.Application(); 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 12: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/luo/demo/Application; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=2, args_size=1 0: iconst_3 1: anewarray #2 // class java/lang/String 4: dup 5: iconst_0 6: ldc #3 // String I 8: aastore 9: dup 10: iconst_1 11: ldc #4 // String love 13: aastore 14: dup 15: iconst_2 16: ldc #5 // String you 18: aastore 19: invokestatic #6 // Method cn/hutool/core/collection/CollectionUtil.newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList; 22: astore_1 23: aload_1 24: invokedynamic #7, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; 29: invokevirtual #8 // Method java/util/ArrayList.forEach:(Ljava/util/function/Consumer;)V 32: return LineNumberTable: line 15: 0 line 16: 23 line 19: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 23 10 1 list Ljava/util/ArrayList; LocalVariableTypeTable: Start Length Slot Name Signature 23 10 1 list Ljava/util/ArrayList<Ljava/lang/String;>; 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 #9 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return LineNumberTable: line 17: 0 line 18: 7 LocalVariableTable: Start Length Slot Name Signature 0 8 0 item Ljava/lang/String; }
經過字節碼能夠看出,調用lambda方法時使用了invokedynamic,該字節碼命令是爲了支持動態語言特性而在Java7中新增的。Java的lambda表達式實現上也就藉助於invokedynamic命令。
字節碼中每一處含有invokeDynamic指令的位置都稱爲「動態調用點」,這條指令的第一個參數再也不是表明方法調用符號引用的CONSTANT_Methodref_info常亮,而是變成爲JDK7新加入的CONSTANT_InvokeDynamic_info常量,從這個新常量中可獲得3項信息:引導方法(Bootstrap Method,此方法存放在新增的BootstrapMethods屬性中)、方法類型和名稱。引導方法是有固定的參數,而且返回值是java.lang.invoke.CallSite對象,這個表明真正要執行的目標方法調用。根據CONSTANT_InvokeDynamic_info常量中提供的信息,虛擬機能夠找到並執行引導方法,從而得到一個CallSite對象,最終調用要執行的目標方法。
從上述mian方法的字節碼可見,有一個invokeDynamic指令,他的參數爲第7項常量(第二個值爲0的參數HotSpot中用不到,佔位符):
invokedynamic #7, 0 // InvokeDynamic #0:accept ()Ljava/util/function/Consumer;
常量池中第7項是#7 = InvokeDynamic #0:#48 // #0:accept:()Ljava/util/function/Consumer;,說明它是一項CONSTANT_InvokeDynamic_info常量,常量值中前面的#0表示引導方法取BootstrapMethods屬性表的第0項,然後面的#48表示引用第48項類型爲CONSTANAT_NameAndType_info的常量,從這個常量中能夠獲取方法名稱和描述符,即accept方法。
BootstrapMethods: 0: #44 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: #45 (Ljava/lang/Object;)V #46 invokestatic com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V #47 (Ljava/lang/String;)V
上圖是在lambda代碼中打斷點時的調用棧信息,若是在這裏的lambda中打印當前所屬class,就是Application類,也印證了前面分析的lambda代碼會生成一個private方法。
從調用棧的信息來看,是在accept方法中調用lambda對應的private方法(ambda$main$0)的,可是這裏的accept方法是屬於什麼對象呢?從圖中看是一串數字字符串,這裏能夠理解成一個Consumer接口的實現類便可,每一個lambda表達式能夠理解成在一個新的Consumer實現類中調用的便可。使用命令jmap -histo查看JVM進程類和對象信息能夠看到這一行信息:
600: 1 16 com.luo.demo.Application$$Lambda$5/1615039080
參考資料: