Java – Guida al costrutto “for-each”: introduzione

Questa prima parte della mia Guida al costrutto “for-each” serve per fornire una introduzione generale al costrutto for-each disponibile da Java 5. Il contenuto di questo articolo è più che altro “discorsivo” e teorico ma serve come base per il resto della guida.

Introduzione

Per iniziare a comprendere il for-each credo sia necessario partire dagli aspetti più basilari, ovvero a cosa serve e perché è stato introdotto nel linguaggio Java. Questo costrutto ha un obiettivo fondamentalmente molto semplice: dare la possibilità di iterare (cioè di “scorrere” uno per uno) gli elementi di una certa “struttura dati” in una maniera molto più pratica e soprattutto più concisa.

Il for-each è stato ideato per operare solo su due tipi di dati:

  • gli array (di qualunque tipo, sia di tipi primitivi che di tipi reference, ovvero di oggetti)
  • gli oggetti di classi che implementano la interfaccia Iterable

Prima di Java 5 per poter iterare su un array si doveva usare il classico ciclo for che faceva incrementare una variabile di “indice” da 0 a array.length-1 (entrambi compresi) e poi ovviamente accedere agli elementi con array[indice] . Invece per iterare su una collezione del framework si doveva prendere esplicitamente un oggetto Iterator e sfruttare i suoi metodi hasNext() e next(), tipicamente all’interno di un ciclo while o for. Questo modo di iterare su array/collezioni non era di per sé “difficile”, era solamente un po’ noioso e prolisso (specialmente nel caso di Iterator).

Il nuovo costrutto for-each permette in sostanza di fare esattamente le stesse cose che ho appena descritto ma potendo scrivere semplicemente meno codice nel sorgente. Il for-each, in pratica, non permette di fare nulla di più di quello che si poteva fare in precedenza. La sua caratteristica principale è appunto solo il fatto di offrire una forma molto abbreviata e compatta per iterare su array o in generale su oggetti che rappresentano un “insieme” di dati.

In effetti il for-each è uno di quei costrutti che vengono genericamente definiti “zucchero sintattico”. Quando si usa il for-each in un sorgente, il compilatore in realtà genera nel byte-code della classe sostanzialmente lo stesso codice che si doveva scrivere in precedenza “a mano” per compiere la iterazione. Il fatto che venga generato del codice che resta “nascosto”, come si vedrà più avanti, rende inaccessibili alcune cose per cui certe operazioni non sono più fattibili.

Tutto questo però non deve far pensare che il for-each sia poco utile. Anzi, invece è molto utile in generale! Semplicemente va usato nei casi più comuni e tipici in cui l’unica cosa che conta è scorrere uno per uno gli elementi di un array o una collezione, senza altre necessità particolari.

Forma generale

Il costrutto for-each ha la seguente forma generale:

for ( [modificatori] tipo identificatore : espressione )
   istruzione

Dove:

tipo identificatore è la dichiarazione di una variabile che ad ogni ciclo conterrà un elemento (attenzione, non l’indice!) dato dalla iterazione. È bene chiarire subito che la dichiarazione di questa variabile non è facoltativa ma obbligatoria. Non si può ri-utilizzare una variabile già dichiarata in precedenza.

Lo “scope”, ovvero l’ambito di visibilità, di questa variabile è limitato al corpo del ciclo for-each. In altre parole, questa variabile “esiste” solo all’interno del ciclo for-each. Questo permette ad esempio di scrivere un metodo in cui ci sono due cicli for-each distinti, slegati (non “annidati”, per essere chiari), che dichiarano lo stesso nome di variabile. Di per sé non c’è alcun errore o conflitto, semplicemente sono due variabili distinte in due blocchi separati.

Il tipo della variabile dichiarata deve essere compatibile con gli elementi dell’array/collezione. Se si sta iterando su un array String[] la variabile può essere di tipo String oppure ad esempio CharSequence o Object (poiché super-tipi di String). Se invece si sta iterando su un array Object[] anche se l’array contenesse solo stringhe la variabile deve essere dichiarata di tipo Object. Non è prevista alcuna possibilità di specificare un cast “esplicito”, gli elementi dati dalla iterazione devono essere implicitamente assegnabili alla variabile dichiarata.

modificatori (opzionale) indica degli eventuali modificatori che si possono applicare alla variabile dichiarata. È possibile specificare delle annotation oppure il modificatore final per rendere la variabile non-modificabile. Generalmente è raro dover mettere un modificatore nel for-each ma la possibilità comunque esiste, se fosse necessario (a me personalmente non è mai capitato finora).

espressione è una qualunque espressione, anche complessa, che rappresenta il “target” del for-each e deve essere un valore il cui tipo denota un array o una implementazione della interfaccia Iterable. La verifica che il tipo sia un array o implementi Iterable viene fatta “staticamente” a livello di compilazione, non a runtime. Se ad esempio un array o una collezione vengono fatti “vedere” al for-each come java.lang.Object ... il codice non compila, Object infatti non implementa Iterable.

C’è una cosa veramente importante da sapere e ricordare: questa espressione non deve valere null, perché altrimenti il for-each lancia subito un NullPointerException. Se si ha una certa espressione che si intende usare nel for-each e si ha idea che essa possa essere anche null (per qualunque motivo), allora è necessario fare prima come minimo un test, banalmente con un semplice if.

istruzione rappresenta il “corpo” del ciclo for-each e può essere una singola istruzione oppure un insieme di istruzioni racchiuse in un blocco { } . Questo è assolutamente ed esattamente lo stesso concetto che esiste anche negli altri costrutti if, for (classico), while, ecc...

Label (etichette), break e continue

Il for-each, esattamente come per gli altri costrutti di ciclo di Java, permette di usare le label (“etichette”) insieme alle istruzioni break e continue. Il concetto e l'uso di queste cose è assolutamente uguale agli altri cicli. Se già conoscete break e continue, è sufficiente dire che non ci sono novità a riguardo nel for-each. Se non li conoscete, non è un problema e non credo sia un ostacolo per la comprensione di questo articolo e dei successivi che seguono sul for-each.