Se i costrutti condizionali visti nella precedente sezione rispondono all'esigenza di eseguire diverse parti di un codice subordinatamente ad una determinata condizione, i costrutti iterativi ci consentono di eseguire ripetutamente un determinato blocco di codice (che, al solito, potrà essere una singola linea oppure un intero blocco racchiuso tra parentesi graffe '{}').
In Java i costrutti iterativi sono sostanzialmente 3, comunemente denominati in base alle keywords che li contraddistinguono: while, do-while e for.
while
Il ciclo while esegue una istruzione o un blocco di codice finché rimane verificata una certa condizione. In italiano diremmo: "fino a quando la condizione è vera esegui il blocco" ecco un esempio:
while(condizione) {
// ...
}
dove condizione
espressione di tipo booleano
condizione
true
Interessante capire cosa succede la prima volta che si accede al ciclo. Se condizione
è false
quando l'esecuzione arriva per la prima volta allo statement while
il blocco di codice non sarà eseguito neanche una volta.
do-while
il ciclo do-while è simile al while
tanto da poter essere considerato una sua variante, ed infatti il frammento di codice:
do {
// ...
} while(condizione);
analogamente a quello sopra presentato, causa l'esecuzione del blocco tra graffe fino a quando la condizione è vera ma con la importante differenza che in questo caso condizione viene presa in considerazione alla fine del blocco. Quindi l'esecuzione del blocco di istruzioni viene effettuata almeno una volta, anche se la condizione risulta da subito false
.
Continuando il parallelo con la lingua italiana questo codice andrebbe letto: "esegui il blocco di codice e, poi, se condizione è vera fallo di nuovo, altrimenti smetti".
for
Il ciclo for è un costrutto tra i più conosciuti, comune praticamente a tutti i linguaggi e, pur servendo come i precedenti ad eseguire ripetutamente un blocco, fornisce una semplice sintassi per accomodare:
- una espressione di inizializzazione eseguita solo una volta prima di iniziare il ciclo;
- una espressione di 'aggiornamento' (tipicamente un incremento) da eseguire al termine di ogni esecuzione del blocco;
- una condizione di terminazione (o uscita) dall'esecuzione iterativa.
Con un codice simile al seguente:
for(inizializzazione; condizione; incremento) {
// ...
}
si ottiene un programma che esegue esattamente una volta inizializzazione, esegue poi il blocco, quindi effettua l'incremento, valuta condizione e, se questa risulta vera (true
boolean
È semplice convincersi con un esempio che un ciclo for ed uno while sono facilmente intercambiabili:
int i=0;
while(i < 10) {
// ...
i++;
}
è identico a:
for(int i=0; i<10; i++) {
// ...
}
dove si vede anche la comune pratica di definire le variabili di iterazione (i
Cicli infiniti
Si osserva che essendo inizializzazione
, incremento
e condizione
opzionali non è strano trovare casi in cui vengano omesse fino all'estremo:
for(;;) {
// ...
}
letto anche "for-ever
ciclo infinito
true
while(true) {}
for each
Una importante variante del ciclo for
è quella che potremmo definire for-each (detta anche for-in) che serve nel caso particolare (ma comune) in cui si voglia eseguire un determinato blocco di codice per ogni elemento di una data collezione (o array).
Pur dovendo rimandare una completa descrizione di questa variante a quando parleremo di Collection e array possiamo comunque mostrarne la sintassi:
for( Type item : itemCollection ) {
// ...
}
che, continuando i paralleli con la lingua italiana si legge come:
"prendi uno ad uno gli elementi della collezione itemCollection
, assegna ciascuno di essi alla variabile item
ed esegui per ciascun elemento il blocco (che potrà quindi usare item
al suo interno)".
for-each, le Collection e i tipi generics
Anche se parleremo più avanti di "Collection" e "generics", in questa fase ci basta sapere che si tratta di collezioni di oggetti alle quali si applicano i cosiddetti iteratori per effettuare una scansione di tutti gli elementi.
Ecco un esempio pratico di una tipica routine di iterazione degli elementi di una Collection che utilizzi i tipi generics:
Queue<String> queue = new LinkedList<String>();
for(Iterator<String> it = queue.iterator(); it.hasNext(); ) {
String tmp = it.next();
// ... qui fa qualcosa
}
Senza l'ausilio dei tipi generics la cosa diventa ancora più ardua, poiché bisogna effettuare il cast (su it.next()
Come abbiamo visto invece, il ciclo for-each permette una definizione automatica di tutto ciò in un solo comando integrato:
for(String tmp:queue) {
//...
}
L'utilizzo della struttura nel secondo esempio verrà tradotto con la codifica definita nel primo, facendoci però perdere qualsiasi riferimento all'iteratore che lavorerà dietro le quinte. Si tratta quindi di una semplificazione che sicuramente dà dei benefici in termine di migliore codifica ma assolutamente non intacca le prestazioni né in positivo né in negativo.
public class ForeachTest {
public static void main(String[] args) {
Collection coll = new ArrayList<String>();
// utilizziamo l'array degli argomenti con for-each
// e popoliamo la collezione
for(String tmp:args){
coll.add(tmp);
}
// stampiamo la collezione
for(String tmp:coll) {
System.out.println(tmp);
}
}
}
Per semplicità lavoriamo con un array di String (l'array degli argomenti, args
Collection<String>
tmp