Un'espressione lambda crea un oggetto sull'heap ogni volta che viene eseguita?


182

Quando eseguo l'iterazione su una raccolta utilizzando il nuovo zucchero sintattico di Java 8, ad esempio

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

Questo non equivale al frammento di "vecchia sintassi" qui sotto?

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

Questo significa che un nuovo Consumeroggetto anonimo viene creato sull'heap ogni volta che eseguo l'iterazione su una raccolta? Quanto spazio heap richiede? Quali sono le implicazioni sulle prestazioni? Significa che dovrei piuttosto usare il vecchio stile per i loop quando si scorre su grandi strutture dati multi-livello?


48
Risposta breve: no. Per gli lambda apolidi (quelli che non catturano nulla dal loro contesto lessicale), solo un'istanza verrà mai creata (pigramente) e memorizzata nella cache nel sito di acquisizione. (Ecco come funziona l'implementazione; le specifiche sono state accuratamente scritte per consentire, ma non richiedono, questo approccio.)
Brian Goetz,

Risposte:


158

È equivalente ma non identico. Detto semplicemente, se un'espressione lambda non acquisisce valori, sarà un singleton che verrà riutilizzato in ogni invocazione.

Il comportamento non è esattamente specificato. Alla JVM viene data grande libertà su come implementarla. Attualmente, la JVM di Oracle crea (almeno) un'istanza per espressione lambda (ovvero non condivide un'istanza tra espressioni identiche diverse) ma crea singleton per tutte le espressioni che non acquisiscono valori.

Puoi leggere questa risposta per maggiori dettagli. Lì, non solo ho dato una descrizione più dettagliata, ma ho anche testato il codice per osservare il comportamento attuale.


Questo è coperto da The Java® Language Specification, capitolo “ 15.27.4. Valutazione run-time delle espressioni lambda

riassunte:

Queste regole hanno lo scopo di offrire flessibilità alle implementazioni del linguaggio di programmazione Java, in quanto:

  • Non è necessario assegnare un nuovo oggetto ad ogni valutazione.

  • Gli oggetti prodotti da diverse espressioni lambda non devono appartenere a classi diverse (se i corpi sono identici, ad esempio).

  • Ogni oggetto prodotto dalla valutazione non deve necessariamente appartenere alla stessa classe (le variabili locali acquisite potrebbero essere incorporate, ad esempio).

  • Se è disponibile una "istanza esistente", non è necessario che sia stata creata in una precedente valutazione lambda (potrebbe essere stata allocata durante l'inizializzazione della classe che la racchiude, ad esempio).


24

Quando un'istanza che rappresenta la lambda viene creata in modo sensibile dipende dal contenuto esatto del corpo della tua lambda. Vale a dire, il fattore chiave è ciò che la lambda cattura dall'ambiente lessicale. Se non acquisisce alcuno stato variabile da una creazione all'altra, non verrà creata un'istanza ogni volta che viene inserito il ciclo for-each. Invece verrà generato un metodo sintetico in fase di compilazione e il sito di utilizzo lambda riceverà semplicemente un oggetto singleton che delega a quel metodo.

Si noti inoltre che questo aspetto dipende dall'implementazione e ci si può aspettare raffinamenti e progressi futuri su HotSpot verso una maggiore efficienza. Esistono piani generali per esempio per realizzare un oggetto leggero senza una classe corrispondente completa, che abbia le informazioni sufficienti per inoltrare a un singolo metodo.

Ecco un buon articolo di approfondimento accessibile sull'argomento:

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


0

Stai passando una nuova istanza al forEachmetodo. Ogni volta che lo fai crei un nuovo oggetto ma non uno per ogni iterazione di loop. L'iterazione viene eseguita all'internoforEach metodo utilizzando la stessa istanza dell'oggetto 'callback' fino a quando non viene eseguita con il ciclo.

Quindi la memoria utilizzata dal loop non dipende dalla dimensione della raccolta.

Questo non equivale allo snippet "vecchia sintassi"?

Sì. Ha lievi differenze a un livello molto basso ma non credo che dovresti preoccupartene. Le espressioni di Lamba usano la funzione invokedynamic anziché le classi anonime.


Hai qualche documentazione che specifica questo? È piuttosto un'ottimizzazione interessante.
A. Rama,

Grazie, ma cosa succede se ho una raccolta di raccolte di raccolte, ad esempio quando eseguo una ricerca approfondita su una struttura di dati ad albero?
Bastian Voigt,

2
@ A.Rama Siamo spiacenti, non vedo l'ottimizzazione. È lo stesso con o senza lambda e con o senza for Loop.
aalku,

1
Non è proprio lo stesso, ma sarà comunque necessario al massimo un oggetto per livello di annidamento alla volta, il che è trascurabile. Ogni nuova iterazione di un ciclo interno creerà un nuovo oggetto, molto probabilmente catturando l'elemento corrente dal ciclo esterno. Questo crea una certa pressione GC, ma ancora nulla di cui preoccuparsi davvero.
Marko Topolnik,

4
@aalku: " Ogni volta che lo fai crei un nuovo oggetto ": Non secondo questa risposta di Holger e questo commento di Brian Goetz .
Lii
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.