lambda從入門到精通

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

Collection中的新方法

List.forEach()

該方法的簽名爲void forEach(Consumer<? super E> action),做用是對容器中的每一個元素執行action指定的動做,其中Consumer是個函數接口,裏面只有一個待實現方法void accept(T t)。注意,這裏的Consumer不重要,只須要知道它是一個函數式接口便可,通常使用不會看見Consumer的身影。github

list.forEach(item -> System.out.println(item));
List.removeIf()

該方法簽名爲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));

Stream API

 認識了幾個Java8 Collection新增的幾個方法,在瞭解下Stream API,你會發現它在集合數據處理方面的強大做用。常見的Stream接口繼承關係圖以下:

 

Stream是數據源的一種視圖,這裏的數據源能夠是數組、集合類型等。獲得一個stream通常不會手動建立,而是調用對應的工具方法:
  • 調用Collection.stream()或者Collection.parallelStream()方法
  • 調用Arrays.stream(T[] array)方法
Stream的特性
  • 無存儲。stream不是一種數據結構,它只是某種數據源的一個視圖。本質上stream只是存儲數據源中元素引用的一種數據結構,注意stream中對元素的更新動做會反映到其數據源上的。
  • 爲函數式編程而生。對stream的任何修改都不會修改背後的數據源,好比對stream執行過濾操做並不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新stream。
  • 惰式執行。stream上的操做並不會當即執行,只有等到用戶真正須要結果的時候纔會執行。
  • 可消費性。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。

 Stream Pipelines原理

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 原理

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;
}
View Code

經過字節碼能夠看出,調用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

  

參考資料

一、https://github.com/CarpenterLee/JavaLambdaInternals

二、https://blog.csdn.net/zxhoo/article/details/38387141

相關文章
相關標籤/搜索