lambda從入門到精通


JDK8中包含了許多內建的Java中常用到函數接口,比如Comparator或者Runnable接口,這些接口都增加了@FunctionalInterface注解以便能用在lambda上。

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表示是一個命名方法,將行為像數據一樣進行傳遞。

Collection中的新方法

List.forEach()

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

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

該方法簽名為boolean removeIf(Predicate<? super E> filter),作用是刪除容器中所有滿足filter指定條件的元素,其中Predicate是一個函數接口,里面只有一個待實現方法boolean test(T t),同樣的這個方法的名字根本不重要,因為用的時候不需要書寫這個名字。

// 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)需要實現,顯然該接口是個函數接口。

// List.sort()方法結合Lambda表達式
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());

Map.forEach()

該方法簽名為void forEach(BiConsumer<? super K,? super V> action),作用是對Map中的每個映射執行action指定的操作,其中BiConsumer是一個函數接口,里面有一個待實現方法void accept(T t, U u)。

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

  

參考資料

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM