2017-07-29 1 views
7

ich die Umsetzung der funktionalen Schnittstelle wurde die Erstellung, unten ist mein CodeWie Speicher Lambda | zugewiesen wird Wie funktioniert es refered, durch nicht Superklassenreferenzvariable

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

Per javadoc

Eine Variable eines T-Klasse-Typ kann hält eine Nullreferenz oder einen Verweis auf eine Instanz der Klasse T oder einer Klasse, die eine Unterklasse von T. ist

Hier Objekt anonymisierter Objekt erstellt, die Unterklasse von Consumer ist und kann Referenzvariable consumer Bezug nehmen, die Fine ist.

Aber ich sah Consumer ist FunctionalInterface, so kann ich auch in java8-

Mit Lambda

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

ODER Unter Verwendung der Methode Referenz

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

etwas tun Was ich kenne keine Unterklassen oder Anonymous cl Esel sowohl die oben cases.So in erstellt wird dies mir zwei confusion-

Ergebnisse

1: Wird hier consumer nicht zu Unterklasse oder die anonyme Klasse von Consumer Bezug genommen wird, so ist nicht nur das das oben erwähnte Konzept Variable verletzen können beziehen Kind/selbst oder null?

2: Wie Speicher zu Lamdas zuweisen und wie JVM solche zur Laufzeit behandelt?

+1

Dort * sind * Klassen. Es ist Rauch und Spiegel. – user2864740

+0

Dies liegt daran, dass JVM eine Unterklasse von 'Consumer' im Speicher für die Bridge-Methoden des Lambda-Ausdrucks-/Methodenreferenzausdrucks erstellt. –

+0

@ holi-java Ich habe keine '.class' Datei gefunden, die das im Klassenpfad betrifft. Kannst du bitte kurz schreiben. – TheCurious

Antwort

4

Sie müssen SE8 verweisen 15.27:

15.27.3. Typ einer Lambda Expression

Ein Lambda-Ausdruck ist kompatibel in einem Zuordnungs Zusammenhang Aufruf Kontext oder Kontext Gießen mit einem T Zielart, wenn T ein funktioneller Schnittstellentyp (§9.8) ist und der Ausdruck ist deckungsgleich mit der Funktionsart vom Typ Bodenziel von T. abgeleitet

Runtime Verwendung Handhabung heikel Dinge mit invokedynamic. Lassen Sie einige Code untersuchen:

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 
    } 
} 

Ausführung gibt:

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

Für die ersten beiden gibt es keine Überraschung ist, die Klasse des durch Verfahren erhalten Objekt execute ist (in der angegebenen Reihenfolge) R (die Untertyp Runnable), L$1 (der anonyme Subtyp von Runnable und L$$Lambda$1/791452441 (der Subtyp von Runnable, konstruiert zur Laufzeit aus dem Lambda).Beachten Sie, dass es im Fall von Lambda keine .class Datei gibt, der Typ wird zur Laufzeit von einem speziellen Konstrukt konstruiert. Lassen Sie sich die Bytecode untersuchen:

> 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 

Der erste interessante Teil ist der Code von main: zwischen der expliziten Implementierung der Schnittstelle oder den anonymen einem

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 

Wie Sie beobachten können, gibt es keinen Unterschied. Die letzte betrifft nur Klassennamen-Tricksereien (L$1), aber beide werden auf die gleiche Weise verwendet: invokestatic.

Interessanter Fall ist der dritte (der Lambda), der invokedynamic und dann invokestatic beinhaltet. Beachten Sie, dass die invokestatic die gleiche Methode wie in den beiden vorherigen Aufrufen (die Methode run) aufruft.

Grob gesagt, das erste Mal, wenn die invokedynamic ist ein Bootstrap-Verfahren genannt wird, bezeichnet ein CallSite (siehe CallSite in Java API), die konstruieren wird dann weiter verwendet werden, um den Code des Lambdas auszuführen. Siehe die Bootstrap-Aufruf hier:

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 

und die von der Aufrufstelle bezeichnet Code:

#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

Die Bootstrap-Methode erstellt die 'class'-Datei für das Lambda (sagen wir eine Klasse, die das' java.util.Predicate' erweitert). Dann ruft intern dieser Prädikat # -Test den entzuckerten Lambda-Ausdruck auf (eine statische innere Methode innerhalb der Klasse, in der Lambda verwendet wird). Es wird auch ein "MethodHandle" für die "test" -Methode erstellt (die in einem 'CallSite'-Objekt enthalten sein wird); Diese 'CallSite' wird genau einmal mit dem Aufruf verknüpft. – Eugene

3

Q: Wenn hier der Verbraucher nicht bezieht sich auf eine Unterklasse oder die anonyme Klasse von Consumer .... ?

Tatsächlich ist die Unterklasse in Verknüpfung Phase von invokedynamc Anweisung eingeführt.

Linkage kann es sich um dynamisch Laden einer neuen Klasse, die die Zielschnittstelle implementiert. Die CallSite kann als "Factory" für Funktionsobjekte betrachtet werden und so werden diese Verknüpfungsmethoden als "Metafactories" bezeichnet.

Q: Wie Speicher lamdas und wie funktioniert JVM Handle so zur Laufzeit zuweisen?

Es geht um durch drei Phasen:

  • Linkage - beinhalten kann dynamisch eine neue Klasse geladen, die das Ziel-Schnittstelle implementiert.
  • Erfassen - Erstellen eines Funktionsobjekts.
  • Aufruf - Eine implementierte Schnittstellenmethode wird für ein Funktionsobjekt aufgerufen.

Für weitere Details können Sie LambdaMetafactory als weitere sehen.

4

Zunächst hat Jean-Baptiste Ihnen gezeigt, warum die Aufgabe überhaupt funktioniert.

Jetzt ist der Teil, den ich denke, dass Sie ist die Tatsache, fehlen, dass die erzeugte Klasse von Consumer bei Consumer<Integer> consumer = t -> System.out.println(t); zur Laufzeit aufgrund invokedynamic nur sichtbar ist.

Führen Sie Ihre Klasse mit einer Flagge:

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

Und Sie werden feststellen, dass es eine generierte Klasse (in einem Pfad von Ordnern aus dem Paketnamen der Klasse), die eine Art .class Datei enthält, ist wie dies SOQuestion$$Lambda$1.class.

Wenn Sie dekompilieren, dass Sie sehen, dass es tatsächlich eine Klasse, die implementiert Consumer:

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

Wenn Sie an dem erzeugten Bytecode sehen, wo Ihre Lambda definiert ist, werden Sie sehen, dass der Lambda-Ausdruck ist eigentlich entzuckert zu einer statischen Methode (für nicht erfassende Lambdas könnte es auch nicht-statisch sein, wenn es ein erfassendes Lambda ist). Der Code dafür:

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 

Soweit wie die VM schafft es Speicher für ein Nicht-Erfassung Lambda Sie einen Singleton bekommen, für ein Capturing-Lambda werden Sie eine neue Instanz von jedem Anruf erhalten. Das absolute Muss ist here

+2

Hey, was machst du? Sie haben es ausführlicher erläutert, was ich gesagt habe, und die Antwort von [Holger] (https://stackoverflow.com/questions/23983832/is-method-reference-caching-a-good-idea-in-java-8/23991339 # 23991339) hat das "* es kann eine neue Klasse in der Verknüpfungsphase enthalten *" genau beschrieben, also hast du meine +1. Gut gemacht, :) –

Verwandte Themen