Un’espressione lambda crea un object sull’heap ogni volta che viene eseguito?

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

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

Di seguito, non è equivalente allo snippet ‘old syntax’?

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

Significa che un nuovo object Consumer anonimo viene creato sull’heap ogni volta che viene ripetuto su una raccolta? Quanto spazio ci vuole? Quali implicazioni sulla performance ha? Vuol dire che dovrei usare il vecchio stile per i loop quando si itera su grandi strutture di dati multilivello?

È 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. La JVM ha una grande libertà su come implementarla. Attualmente, JVM di Oracle crea (almeno) un’istanza per espressione lambda (cioè non condivide l’istanza tra diverse espressioni identiche) 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 corrente.


Questo è coperto da The Java® Language Specification, capitolo ” 15.27.4. Valutazione run-time di Lambda Expressions ”

riassunte:

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

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

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

  • Non è necessario che tutti gli oggetti prodotti dalla valutazione appartengano alla stessa class (le variabili locali catturate potrebbero essere inline, 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 class di inclusione, ad esempio).

Quando un’istanza che rappresenta il lambda è creata sensibilmente dipende dal contenuto esatto del corpo del tuo lambda. Vale a dire, il fattore chiave è ciò che la lambda cattura dall’ambiente lessicale. Se non acquisisce nessuno stato che è variabile dalla creazione alla creazione, allora un’istanza non verrà creata ogni volta che viene immesso il ciclo for-each. Invece un metodo sintetico verrà generato in fase di compilazione e il sito di uso di lambda riceverà solo un object singleton che delega a tale metodo.

Nota inoltre che questo aspetto dipende dall’implementazione e puoi aspettarti miglioramenti e avanzamenti futuri su HotSpot verso una maggiore efficienza. Ci sono piani generali per fare, ad esempio, un object leggero senza una class corrispondente completa, che ha solo abbastanza informazioni da inoltrare a un singolo metodo.

Ecco un buon articolo accessibile e approfondito sull’argomento:

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

Stai passando una nuova istanza al metodo forEach . Ogni volta che lo fai crei un nuovo object ma non uno per ogni iterazione del ciclo. L’iterazione viene eseguita all’interno del metodo forEach utilizzando la stessa istanza dell’object “callback” finché non viene eseguita con il ciclo.

Quindi la memoria utilizzata dal ciclo non dipende dalla dimensione della collezione.

Non è equivalente allo snippet ‘old syntax’?

Sì. Ha lievi differenze a un livello molto basso ma non penso che dovresti preoccuparti di loro. Le espressioni Lamba utilizzano la funzione invokedynamic invece delle classi anonime.

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

Sulla mia scatola mostra lo stesso object:

 142257191
 142257191

Quando si utilizza il riferimento al metodo, viene creato un object diverso:

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

Produzione :

 1044036744
 1826771953

macOS Alta Sierra
versione java "1.8.0_181"
Java (TM) SE Runtime Environment (build 1.8.0_181-b13)
VM server Java HotSpot (TM) a 64 bit (build 25.181-b13, modalità mista)