Maakt een lambda-expressie een object op de heap elke keer dat deze wordt uitgevoerd?

Als ik een verzameling herhaal met de nieuwe syntactische suiker van Java 8, zoals

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

Is dit niet gelijk aan het fragment ‘oude syntaxis’ hieronder?

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

Betekent dit dat er elke keer dat ik een verzameling doorloop, een nieuw anoniem Consumer-object op de heap wordt gemaakt? Hoeveel hoopruimte neemt dit in beslag? Welke prestatie-implicaties heeft het? Betekent dit dat ik liever de oude stijl voor loops moet gebruiken bij het herhalen van grote datastructuren op meerdere niveaus?


Antwoord 1, autoriteit 100%

Het is gelijkwaardig maar niet identiek. Simpel gezegd, als een lambda-expressie geen waarden vastlegt, zal het een singleton zijn die bij elke aanroep opnieuw wordt gebruikt.

Het gedrag is niet precies gespecificeerd. De JVM krijgt grote vrijheid bij de implementatie ervan. Momenteel maakt de JVM van Oracle (ten minste) één instantie per lambda-expressie (d.w.z. deelt geen instantie tussen verschillende identieke expressies), maar creëert singletons voor alle expressies die geen waarden vastleggen.

Je kunt dit antwoordlezen voor meer details. Daar heb ik niet alleen een meer gedetailleerde beschrijving gegeven, maar ook code getest om het huidige gedrag te observeren.


Dit wordt behandeld in de Java®-taalspecificatie, hoofdstuk “15.27.4. Runtime-evaluatie van Lambda-expressies

Samengevat:

Deze regels zijn bedoeld om flexibiliteit te bieden aan implementaties van de Java-programmeertaal, in die zin dat:

  • Er hoeft niet bij elke evaluatie een nieuw object te worden toegewezen.

  • Objecten die door verschillende lambda-expressies worden geproduceerd, hoeven niet tot verschillende klassen te behoren (bijvoorbeeld als de lichamen identiek zijn).

  • Elk object dat door evaluatie wordt geproduceerd, hoeft niet tot dezelfde klasse te behoren (vastgelegde lokale variabelen kunnen bijvoorbeeld inline zijn).

  • Als een “bestaande instantie” beschikbaar is, hoeft deze niet te zijn gemaakt bij een eerdere lambda-evaluatie (deze kan bijvoorbeeld zijn toegewezen tijdens de initialisatie van de omsluitende klasse).


Antwoord 2, autoriteit 15%

Wanneer een instantie die de lambda vertegenwoordigt, gevoelig wordt gemaakt, hangt af van de exacte inhoud van het lichaam van je lambda. De belangrijkste factor is namelijk wat de lambda haaltuit de lexicale omgeving. Als het geen enkele toestand vastlegt die variabel is van creatie tot creatie, dan zal er geen instantie gemaakt worden telkens wanneer de for-each lus wordt ingevoerd. In plaats daarvan wordt tijdens het compileren een synthetische methode gegenereerd en ontvangt de lambda-gebruikssite slechts een singleton-object dat naar die methode wordt gedelegeerd.

Houd er verder rekening mee dat dit aspect afhankelijk is van de implementatie en dat u toekomstige verfijningen en verbeteringen op HotSpot kunt verwachten voor meer efficiëntie. Er zijn algemene plannen om b.v. maak een lichtgewicht object zonder een volledige corresponderende klasse, die net genoeg informatie heeft om door te sturen naar een enkele methode.

Hier is een goed, toegankelijk en diepgaand artikel over het onderwerp:

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


Antwoord 3

U geeft een nieuwe instantie door aan de methode forEach. Elke keer dat u dat doet, maakt u een nieuw object aan, maar niet één voor elke lus-iteratie. Iteratie wordt gedaan binnen de forEachmethode met dezelfde ‘callback’ objectinstantie totdat het klaar is met de lus.

Het geheugen dat door de lus wordt gebruikt, is dus niet afhankelijk van de grootte van de verzameling.

Is dit niet gelijk aan het fragment ‘oude syntaxis’?

Ja. Het heeft kleine verschillen op een zeer laag niveau, maar ik denk niet dat je om hen zou geven. Lamba-uitdrukkingen gebruiken de Invokedynamic-functie in plaats van anonieme klassen.

Other episodes