Le prestazioni dipendono da tanti fattori, che è difficile da prevedere. Normalmente, diremmo, se il vostro supervisore afferma che c'è stato un problema con le prestazioni, il vostro supervisore è incaricato di spiegare quale problema.
Una cosa di cui qualcuno potrebbe aver paura è che dietro le quinte viene generata una classe per ogni sito di creazione lambda (con l'implementazione corrente), quindi se il codice in questione viene eseguito solo una volta, questo potrebbe essere considerato un spreco di risorse. Questo si armonizza con il fatto che le espressioni lambda hanno un overhead di inizializzazione più elevato del codice imperativo ordinario (non stiamo confrontando qui le classi interne), quindi gli inizializzatori di classi all'interno, che vengono eseguiti solo una volta, potrebbero essere considerati evitabili. Questo è anche in linea con il fatto che dovresti never use parallel streams in class initializers, quindi questo potenziale vantaggio non è comunque disponibile qui.
Per codice ordinario, eseguito frequentemente che è probabilmente ottimizzato dalla JVM, questi problemi non si presentano. Come si suppone correttamente, le classi generate per le espressioni lambda ottengono lo stesso trattamento (ottimizzazioni) delle altre classi. In questi luoghi, chiamare forEach
sulle raccolte ha il potenziale di essere più efficiente di di un ciclo for
.
Le istanze di oggetti temporanei creati per Iterator
o l'espressione lambda sono trascurabili, tuttavia, potrebbe essere interessante notare che un ciclo foreach sarà sempre creare un'istanza Iterator
che lambda expression do not always do. Mentre l'implementazione default
di Iterable.forEach
crea anche uno Iterator
, alcune delle raccolte più utilizzate sfruttano l'opportunità di fornire un'implementazione specializzata, in particolare ArrayList
.
Il forEach
ArrayList
è fondamentalmente un ciclo for
su un array, senza alcun Iterator
. Quindi invocherà il metodo accept
dello Consumer
, che sarà una classe generata contenente una delega banale al metodo sintetico contenente il codice dell'espressione lambda. Per ottimizzare l'intero ciclo, l'orizzonte dell'ottimizzatore deve estendersi sul loop di ArrayList
su un array (un linguaggio comune riconoscibile per un ottimizzatore), il metodo sintetico accept
contenente una delega banale e il metodo contenente il codice effettivo.
Al contrario, quando l'iterazione nello stesso elenco utilizzando un ciclo foreach, un Iterator
attuazione viene creata contenente la ArrayList
iterazione logica, si sviluppa su due metodi, hasNext()
e next()
e istanza variabili del Iterator
. Il loop ripetutamente richiamare il metodo hasNext()
controlla la condizione finale (index<size
) e next()
che riverificare la condizione prima di ritornare l'elemento, in quanto non v'è alcuna garanzia che il chiamante non correttamente invoca hasNext()
prima next()
. Ovviamente, un ottimizzatore è in grado di rimuovere questa duplicazione, ma ciò richiede uno sforzo maggiore rispetto al non averlo in primo luogo. Quindi, per ottenere le stesse prestazioni del metodo forEach
, l'orizzonte dell'ottimizzatore deve estendere il codice loop, l'implementazione non banale hasNext()
e l'implementazione non banale next()
.
Le cose simili possono essere applicate anche ad altre raccolte con un'implementazione specializzata forEach
. Questo vale anche per le operazioni di streaming, se la sorgente fornisce un'implementazione specializzata Spliterator
, che non diffonde la logica di iterazione su due metodi come uno Iterator
.
Quindi, se si desidera discutere gli aspetti tecnici di for
ciascuno contro forEach(…)
, è possibile utilizzare queste informazioni.
Tuttavia, come detto, questi aspetti descrivono solo potenziali aspetti di prestazioni poiché il lavoro dell'ottimizzatore e altri aspetti ambientali di runtime possono modificare completamente l'esito. Penso che, come regola generale, più piccolo è il corpo/azione del loop, più appropriato è il metodo forEach
. Questo si armonizza perfettamente con la linea guida di evitare espressioni lambda troppo lunghe comunque.
I flussi paralleli possono essere più lenti dei normali cicli se le richieste della CPU e/o le dimensioni del flusso non sono grandi. I thread sono costosi da gestire. – Bohemian
Il sovraccarico del framework del flusso potrebbe essere sorprendente. Misurare! –
In effetti e così ho aggiornato la mia domanda. – Konstantine