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

相关文章
相关标签/搜索