2017-07-29 60 views
7

我创建功能接口的实现,下面是我的代码内存如何分配给lambda |它是如何refered,非超类引用变量

Consumer<Integer> consumer=new Consumer<Integer>() { 
    @Override 
    public void accept(Integer t) { 
     System.out.println(t); 
    } 
}; 

javadoc

类类型T的变量可以请将空引用或引用 应用于T类或T类子类的任何类的实例。

此处匿名对象创建的对象,它是Consumer的子类,可以通过引用变量consumer来引用,这很好。

但是我看到ConsumerFunctionalInterface,所以我也可以做这样的事情在java8-

使用lambda

Consumer<Integer> consumer=t->System.out.println(t); 

或使用方法参考

Consumer<Integer> consumer=System.out::println; 

我知道没有子类或匿名CL在上述两种cases.So创建驴这导致了我两个confusion-

1:如果这里consumer是不是指子类或匿名类Consumer,所以这不就是违反上述概念变量不仅可以请参考孩子/自我或null

2:内存如何分配给lamdas以及JVM在运行时如何处理?

+1

*有*类。这是烟雾和镜子。 – user2864740

+0

这是因为JVM将在内存中为lambda表达式/方法引用表达式的桥接方法创建'Consumer'的子类。 –

+0

@ holi-java我在类路径中找不到任何'.class'文件。您可以简要介绍一下。内存中的 – TheCurious

回答

4

您需要参考SE8 15.27:

15.27.3。一个Lambda表达式

lambda表达式是在分配上下文兼容,调用 上下文,或者与目标类型T铸造上下文如果T是一个功能性 接口类型(§9.8)和类型的表达是一致与 从T派生的地面目标类型的函数类型。

运行时处理使用棘手的事情与invokedynamic。让检查一些代码:

import java.util.function.*; 

class R implements Runnable { 
    public void run() { System.out.println("there"); } 
} 

public class L { 
    public static void execute(Runnable r) { 
    System.out.println(r.getClass()); 
    r.run(); 
    } 
    public static void main(String []a) { 
    execute(new R()); // subclass 
    execute(new Runnable() { // anonymous subclass 
     public void run() { System.out.println("elsewhere"); } 
     }); 
    execute(() -> System.out.println("here")); // lambda 
    } 
} 

执行给出:

> java L 
class R 
there 
class L$1 
elsewhere 
class L$$Lambda$1/791452441 
here 

对于前两个没有惊讶的是,类是通过方法execute接收到的对象的(在给定的顺序)R(该(Runnable的子类型),L$1RunnableL$$Lambda$1/791452441的匿名子类型(Runnable的子类型在运行时从lambda构建)。请注意,在lambda的情况下没有.class文件,该类型在运行时由特殊结构构造。让我们来看看字节码:

> javap -c -v L 
Classfile /private/tmp/L.class 
    Last modified 1 août 2017; size 1234 bytes 
    MD5 checksum 9680a2bc143d25344979bae00fff3db7 
    Compiled from "L.java" 
public class L 
    minor version: 0 
    major version: 52 
    flags: ACC_PUBLIC, ACC_SUPER 
Constant pool: 
    #1 = Methodref   #15.#28  // java/lang/Object."<init>":()V 
    #2 = Fieldref   #29.#30  // java/lang/System.out:Ljava/io/PrintStream; 
    #3 = Methodref   #15.#31  // java/lang/Object.getClass:()Ljava/lang/Class; 
    #4 = Methodref   #32.#33  // java/io/PrintStream.println:(Ljava/lang/Object;)V 
    #5 = InterfaceMethodref #34.#35  // java/lang/Runnable.run:()V 
    #6 = Class    #36   // R 
    #7 = Methodref   #6.#28   // R."<init>":()V 
    #8 = Methodref   #14.#37  // L.execute:(Ljava/lang/Runnable;)V 
    #9 = Class    #38   // L$1 
    #10 = Methodref   #9.#28   // L$1."<init>":()V 
    #11 = InvokeDynamic  #0:#43   // #0:run:()Ljava/lang/Runnable; 
    #12 = String    #44   // here 
    #13 = Methodref   #32.#45  // java/io/PrintStream.println:(Ljava/lang/String;)V 
    #14 = Class    #46   // L 
    #15 = Class    #47   // java/lang/Object 
    #16 = Utf8    InnerClasses 
    #17 = Utf8    <init> 
    #18 = Utf8    ()V 
    #19 = Utf8    Code 
    #20 = Utf8    LineNumberTable 
    #21 = Utf8    execute 
    #22 = Utf8    (Ljava/lang/Runnable;)V 
    #23 = Utf8    main 
    #24 = Utf8    ([Ljava/lang/String;)V 
    #25 = Utf8    lambda$main$0 
    #26 = Utf8    SourceFile 
    #27 = Utf8    L.java 
    #28 = NameAndType  #17:#18  // "<init>":()V 
    #29 = Class    #48   // java/lang/System 
    #30 = NameAndType  #49:#50  // out:Ljava/io/PrintStream; 
    #31 = NameAndType  #51:#52  // getClass:()Ljava/lang/Class; 
    #32 = Class    #53   // java/io/PrintStream 
    #33 = NameAndType  #54:#55  // println:(Ljava/lang/Object;)V 
    #34 = Class    #56   // java/lang/Runnable 
    #35 = NameAndType  #57:#18  // run:()V 
    #36 = Utf8    R 
    #37 = NameAndType  #21:#22  // execute:(Ljava/lang/Runnable;)V 
    #38 = Utf8    L$1 
    #39 = Utf8    BootstrapMethods 
    #40 = MethodHandle  #6:#58   // 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; 
    #41 = MethodType   #18   // ()V 
    #42 = MethodHandle  #6:#59   // invokestatic L.lambda$main$0:()V 
    #43 = NameAndType  #57:#60  // run:()Ljava/lang/Runnable; 
    #44 = Utf8    here 
    #45 = NameAndType  #54:#61  // println:(Ljava/lang/String;)V 
    #46 = Utf8    L 
    #47 = Utf8    java/lang/Object 
    #48 = Utf8    java/lang/System 
    #49 = Utf8    out 
    #50 = Utf8    Ljava/io/PrintStream; 
    #51 = Utf8    getClass 
    #52 = Utf8    ()Ljava/lang/Class; 
    #53 = Utf8    java/io/PrintStream 
    #54 = Utf8    println 
    #55 = Utf8    (Ljava/lang/Object;)V 
    #56 = Utf8    java/lang/Runnable 
    #57 = Utf8    run 
    #58 = Methodref   #62.#63  // 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; 
    #59 = Methodref   #14.#64  // L.lambda$main$0:()V 
    #60 = Utf8    ()Ljava/lang/Runnable; 
    #61 = Utf8    (Ljava/lang/String;)V 
    #62 = Class    #65   // java/lang/invoke/LambdaMetafactory 
    #63 = NameAndType  #66:#69  // 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; 
    #64 = NameAndType  #25:#18  // lambda$main$0:()V 
    #65 = Utf8    java/lang/invoke/LambdaMetafactory 
    #66 = Utf8    metafactory 
    #67 = Class    #71   // java/lang/invoke/MethodHandles$Lookup 
    #68 = Utf8    Lookup 
    #69 = 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; 
    #70 = Class    #72   // java/lang/invoke/MethodHandles 
    #71 = Utf8    java/lang/invoke/MethodHandles$Lookup 
    #72 = Utf8    java/lang/invoke/MethodHandles 
{ 
    public L(); 
    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 9: 0 

    public static void execute(java.lang.Runnable); 
    descriptor: (Ljava/lang/Runnable;)V 
    flags: ACC_PUBLIC, ACC_STATIC 
    Code: 
     stack=2, locals=1, args_size=1 
     0: getstatic  #2     // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: invokevirtual #3     // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     7: invokevirtual #4     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     10: aload_0 
     11: invokeinterface #5, 1   // InterfaceMethod java/lang/Runnable.run:()V 
     16: return 
     LineNumberTable: 
     line 11: 0 
     line 12: 10 
     line 13: 16 

    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: new   #6     // class R 
     3: dup 
     4: invokespecial #7     // Method R."<init>":()V 
     7: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     10: new   #9     // class L$1 
     13: dup 
     14: invokespecial #10     // Method L$1."<init>":()V 
     17: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     20: invokedynamiC#11, 0    // InvokeDynamiC#0:run:()Ljava/lang/Runnable; 
     25: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     28: return 
     LineNumberTable: 
     line 15: 0 
     line 16: 10 
     line 19: 20 
     line 20: 28 
} 
SourceFile: "L.java" 
InnerClasses: 
    statiC#9; //class L$1 
    public static final #68= #67 of #70; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles 
BootstrapMethods: 
    0: #40 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: 
     #41()V 
     #42 invokestatic L.lambda$main$0:()V 
     #41()V 

第一个有趣的部分是main代码:

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: new   #6     // class R 
     3: dup 
     4: invokespecial #7     // Method R."<init>":()V 
     7: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     10: new   #9     // class L$1 
     13: dup 
     14: invokespecial #10     // Method L$1."<init>":()V 
     17: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     20: invokedynamiC#11, 0    // InvokeDynamiC#0:run:()Ljava/lang/Runnable; 
     25: invokestatic #8     // Method execute:(Ljava/lang/Runnable;)V 
     28: return 

正如你可以看到有在显式接口的实现或匿名一个没有任何区别。最后只涉及类命名欺骗(L$1),但两者都以相同的方式使用,通过invokestatic

有趣的案例是涉及invokedynamic,然后invokestatic第三(拉姆达一)。请注意,invokestatic调用与前两次调用(方法run)相同的方法。

粗略地说,invokedynamic第一次被称为bootstrap方法被调用来构造CallSite(参见CallSite in Java API),然后它将被进一步用来执行lambda的代码。见引导来电来访:

BootstrapMethods: 
    0: #40 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: 
     #41()V 
     #42 invokestatic L.lambda$main$0:()V 
     #41()V 

和代码通过调用现场简称:

#41 = MethodType   #18   // ()V 
    #42 = MethodHandle  #6:#59   // invokestatic L.lambda$main$0:()V 
    #43 = NameAndType  #57:#60  // run:()Ljava/lang/Runnable; 
    #44 = Utf8    here 
    #45 = NameAndType  #54:#61  // println:(Ljava/lang/String;)V 
    #46 = Utf8    L 
    #47 = Utf8    java/lang/Object 
    #48 = Utf8    java/lang/System 
    #49 = Utf8    out 
    #50 = Utf8    Ljava/io/PrintStream; 
    #51 = Utf8    getClass 
    #52 = Utf8    ()Ljava/lang/Class; 
    #53 = Utf8    java/io/PrintStream 
    #54 = Utf8    println 
    #55 = Utf8    (Ljava/lang/Object;)V 
    #56 = Utf8    java/lang/Runnable 
    #57 = Utf8    run 
+0

引导方法将为lambda创建'class'文件(假设一个扩展java.util.Predicate的类)。然后在内部,这个'Predicate#test'将调用de-sugared lambda表达式(在使用lambda的类中的一个静态内部方法)。它还会为'test'方法创建一个'MethodHandle'(它将包含在'CallSite'对象中);这个'CallSite'将被链接到调用,只有一次。 – Eugene

3

Q:如果这里的消费是不是指子类或匿名类消费的.... ?

其实这个子类是在中引入的,链接相的是invokedynamc指令。

链接可能涉及动态加载实现目标接口的新类。对于函数对象,CallSite可被认为是“工厂”,因此这些链接方法被称为“元因子”。

问题:内存如何分配给lamdas以及JVM如何在运行时处理?

它前进,以便通过三个阶段:

  • 联动 - 可以涉及动态加载实现目标接口的新类。
  • 捕获 - 生成一个函数对象。
  • 调用 - 在函数对象上调用实现的接口方法。

有关更多详细信息,请参阅LambdaMetafactory

4

首先,让 - 巴蒂斯特先生向你展示了为什么这项工作首先要完成。

现在,我认为你缺少的一部分的事实是,在Consumer<Integer> consumer = t -> System.out.println(t);情况下,生成的类Consumer只有在运行时由于invokedynamic可见。

具有标志Run类:

java -Djdk.internal.lambda.dumpProxyClasses=/Your/Path 

,你会发现有一个生成的类(从类的你的包名的文件夹的路径内),其中包含一个.class文件有点像这SOQuestion$$Lambda$1.class

如果反编译,你会看到,它实际上是一个实现类Consumer

final class org.eugene.so.SOQuestion$$Lambda$1 
      implements java.util.function.Consumer { 
    public void accept(java.lang.Object); 
} 

如果你看一下在您的拉姆达定义生成的字节代码,你会看到,lambda表达式实际上是去糖加一个静态方法(对于非捕获lambda,如果它是一个捕获lambda,它也可以是非静态的)。它的代码:

private static void lambda$main$0(java.lang.Integer); 
Code: 
    0: getstatic  #3 // Field java/lang/System.out:Ljava/io/PrintStream; 
    3: aload_0 
    4: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
    7: return 

至于​​如何管理内存的它,对于非捕获拉姆达你会得到一个单身,用于捕获 - 拉姆达你会得到每个呼叫的新实例。绝对必读为here

+2

嘿,你在做什么?你已经更详细地解释了我说的和[@ Holger的答案](https://stackoverflow.com/questions/23983832/is-method-reference-caching-a-good-idea-in-java-8/23991339#23991339)已经完全描述了“*它可能包含一个新的班级”,所以你有我的+1。做得好, :) –