Erstellt ein Lambda-Ausdruck bei jeder Ausführung ein Objekt auf dem Heap?

Wenn ich über eine Sammlung mit dem neuen syntaktischen Zucker von Java 8 iteriere, wie z

myStream.forEach(item -> { // do something useful }); 

Entspricht das nicht dem unten stehenden Snippet “alte Syntax”?

 myStream.forEach(new Consumer() { @Override public void accept(Item item) { // do something useful } }); 

Bedeutet das, dass jedes Mal, wenn ich über eine Sammlung iteriere, ein neues anonymes Consumer Objekt auf dem Heap erstellt wird? Wie viel Heap-Platz braucht das? Welche Auswirkungen hat das auf die Performance? Bedeutet das, dass ich lieber den alten Stil für Schleifen verwenden sollte, wenn ich über große mehrstufige Datenstrukturen iteriere?

Es ist äquivalent, aber nicht identisch. Einfach gesagt, wenn ein Lambda-Ausdruck keine Werte erfasst, wird es ein Singleton sein, der bei jedem Aufruf wiederverwendet wird.

Das Verhalten ist nicht genau angegeben. Der JVM wird eine große Freiheit bei der Implementierung gegeben. Gegenwärtig erstellt Oracles JVM (mindestens) eine Instanz pro Lambda-Ausdruck (dh sie teilt keine Instanz zwischen verschiedenen identischen Ausdrücken), sondern erzeugt Singleton für alle Ausdrücke, die keine Werte erfassen.

Sie können diese Antwort für weitere Details lesen. Dort habe ich nicht nur eine ausführlichere Beschreibung gegeben, sondern auch Code getestet, um das aktuelle Verhalten zu beobachten.


Dies wird durch die Java® Language Specification, Kapitel ” 15.27.4. Laufzeitbewertung von Lambda-Expressions

Zusammengefasst:

Diese Regeln sollen Flexibilität für Implementierungen der Java-Programmiersprache bieten, indem:

  • Ein neues Objekt muss nicht bei jeder Auswertung zugewiesen werden.

  • Objekte, die von verschiedenen Lambda-Ausdrücken erzeugt werden, müssen nicht zu verschiedenen classn gehören (wenn die Körper zum Beispiel identisch sind).

  • Jedes Objekt, das durch die Auswertung erzeugt wird, muss nicht zur selben class gehören (erfasste lokale Variablen können zum Beispiel inline sein).

  • Wenn eine “existierende Instanz” verfügbar ist, muss sie nicht bei einer vorherigen Lambda-Auswertung erstellt worden sein (sie könnte beispielsweise während der Initialisierung der umschließenden class zugewiesen worden sein).

Wenn eine Instanz, die das Lambda repräsentiert, sensibel erstellt wird, hängt dies vom genauen Inhalt des Körpers Ihres Lambda ab. Der Schlüsselfaktor ist nämlich, was das Lambda aus der lexikalischen Umgebung erfasst . Wenn kein Status erfasst wird, der von der Erstellung bis zur Erstellung variabel ist, wird bei jedem Eintritt in die for-each-Schleife keine Instanz erstellt. Stattdessen wird zum Zeitpunkt der Kompilierung eine synthetische Methode generiert, und die Lambda-Verwendungsstelle erhält nur ein Singleton-Objekt, das an diese Methode delegiert wird.

Beachten Sie außerdem, dass dieser Aspekt implementationsabhängig ist und Sie zukünftige Verbesserungen und Weiterentwicklungen von HotSpot zu mehr Effizienz erwarten können. Es gibt allgemeine Pläne, z. B. ein leichtgewichtiges Objekt ohne eine vollständige entsprechende class zu erstellen, die gerade genug Informationen enthält, um sie an eine einzige Methode weiterzuleiten.

Hier ist ein guter, ausführlicher Artikel zum Thema:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Sie übergeben eine neue Instanz an die forEach Methode. Jedes Mal, wenn Sie das tun, erstellen Sie ein neues Objekt, aber nicht jedes für jede Schleifeniteration. Die Iteration wird für forEach Methode mit der gleichen “Callback” Objektinstanz ausgeführt, bis sie mit der Schleife fertig ist.

Der von der Schleife verwendete Speicher hängt also nicht von der Größe der Sammlung ab.

Entspricht das dem alten Syntax-Snippet nicht?

Ja. Es hat kleine Unterschiede auf einem sehr niedrigen Niveau, aber ich denke nicht, dass Sie sich um sie kümmern sollten. Lambda-Ausdrücke verwenden das aufgerufene dynamische Feature anstelle von anonymen classn.

 List list = new ArrayList() { @Override public void forEach(Consumer action) { // just print the lambda hash code System.out.println(action.hashCode()); } }; for (int i = 0; i < 2; i++) { list.forEach(o -> { // do something useful }); } 

Auf meiner Box zeigt es sich als das gleiche Objekt:

 142257191
 142257191

Bei Verwendung der Methodenreferenz wird ein anderes Objekt erstellt:

 for (int i = 0; i < 2; i++) { list.forEach(System.out::println); } 

Ausgabe :

 1044036744
 1826771953

macOS Hohe Sierra
Java-Version "1.8.0_181"
Java (TM) SE Laufzeitumgebung (Build 1.8.0_181-b13)
Java HotSpot (TM) 64-Bit-Server-VM (Build 25.181-b13, gemischter Modus)