MokaByte 65 - Luglio Agosto 2002 
Corso introduttivo su Java
V parte: i costrutti decisionali
di
Andrea Gini
Dopo aver introdotto il concetto di variabile e di array, è giunto il momento di analizzare a fondo i restanti costrutti del linguaggio Java. Come già visto nel capitolo 0, i costrutti fondamentali di un linguaggio di programmazione sono quelli decisionali e quelli iterativi. Il linguaggio Java prevede tre costrutti decisionali e tre iterativi: ora li analizzeremo in profondità. Chi avesse familiarità con il linguaggio C troverà familiari i costrutti di Java: esso infatti ne riprende la sintassi con minime variazioni

Costrutti decisionali
Un costrutto decisionale permette al programmatore di vincolare l'esecuzione di un'istruzione (o di un blocco di istruzioni) ad una condizione booleana. Prima di analizzare l'essenza di tali costrutti, è bene chiarire cosa si intenda esattamente con i termini "espressione booleana" e "blocco di istruzioni".

 

Condizioni booleane
Una condizione booleana è un'espressione della quale si può dire se sia vera o falsa (in inglese true o false). Abbiamo già illustrato gli esempi più semplici, che fanno uso degli operatori di uguaglianza, maggiore e minore. In questa sede vale la pena di approfondire la possibilità di combinare le condizioni booleane utilizzando gli operatori logici AND, OR e NOT.

In Java per l'operatore AND viene rappresentato dal carattere "&". L'AND logico opera su due parametri, e restituisce true solamente se entrambi sono true. Se vogliamo che l'istruzione x = x + 1 venga eseguita solo se il valore della variabile x è maggiore di 10 e contemporaneamente minore di 100 (ossia compreso tra 10 e 100) scriveremo:

if(x >= 10 && x <= 100)
x = x + 1;

Si noti che nell'esempio l'operatore & viene ripetuto due volte: questa variante dell'AND, denominata short circuit, segnala al calcolatore che può interrompere la valutazione dell'espressione non appena sia stata verificato il suo valore di verità, migliorando l'efficienza di esecuzione. Ad esempio, se durante la valutazione dell'espressione il calcolatore scopre che il primo parametro di una AND è falso, tutta l'espressione risulterà falsa, indipendentemente dal valore del secondo parametro.

L'operatore OR, rappresentato in Java con il carattere "|", restituisce true se uno o entrambi i parametri sono veri, mentre restituisce false solamente quando entrambi i parametri sono falsi. Pertanto, se vogliamo che l'istruzione x = x * 2 venga eseguita solo se x e' uguale a 7 o ad 8, dovremo scrivere

if ( x == 7 || x == 8 )
x = x * 2;

Anche in questo caso si è fatto ricorso all'operatore di short circuit "||" al fine di rendere più efficiente la valutazione: se il primo parametro è vero, l'espressione è vera indipendentemente dal valore del secondo parametro.

L'operatore XOR (Or Esclusivo), rappresentato in Java con il carattere "^", restituisce true solo se uno dei due parametri e' vero e l'altro falso; se al contrario i parametri sono entrambi veri o entrambi falsi, l'espressione restituisce false. Si noti che non esiste un operatore shortcut per l'Or Esclusivo, dal momento che è necessario valutare entrambi i parametri per fornire un valore di verità.

Infine l'operatore NOT (in Java il carattere "!") permette di negare una qualunque espressione, restituendo in tal modo un valore true se l'espressione è false e viceversa.

Si noti che possiamo scrivere espressioni complicate a piacere, combinando tra loro un numero qualunque di espressioni più semplici e ricorrendo alle parentesi per rendere esplicite le precedenze. La seguente istruzione azzera la variabile x se il suo valore è compreso tra 10 e 20 o tra 30 e 40, estremi inclusi:

if((x>=10 && x <= 20) || (x >= 30 && x <= 40))
x = 0;

 

Blocco di istruzioni
Negli esempi visti fino ad ora, l'istruzione if vincolava l'esecuzione di una unica istruzione. Come ci si deve comportare se si desidera fare in modo di vincolare un numero maggiore di istruzioni? In casi come questi si deve definire un blocco, ossia un insieme di istruzioni racchiuso tra parentesi graffe, che vengono trattate dal compilatore Java come se fossero un'istruzione unica. Ecco allora che, se vogliamo azzerare le variabili x, y e z qualora una di esse superi il valore di 100, possiamo scrivere un frammento di codice del tipo:

if ( x >= 100 || y >= 100 || z >= 100 ) {
  // se la condizione e' vera, tutte le
  // seguenti istruzioni verranno eseguite
  x = 0;
  y = 0;
  z = 0;
}

Per convenzione, quando si apre una parentesi graffa, le righe successive vengono indentate di un paio di spazi. Tale convenzione è del tutto arbitraria: niente impedisce di riscrivere il frammento di codice precedente in questo modo:

if ( x >= 100 || y >= 100 || z >= 100 ){
  x = 0; y = 0; z = 0;
}

Come già visto in precedenza, le convenzioni di impaginazione aiutano a rendere il codice più leggibile, e di conseguenza più facile da correggere o da mantenere.

Una particolarità dei blocchi, è che al loro interno è possibile definire delle variabili locali. Tali variabili hanno una ciclo di vita ridotto, che termina non appena il calcolatore esce dal blocco. Pertanto, in un caso come il seguente:

if ( x != y ) {
  int t = x;
  x = y;
  y = t;
}
t = 0; // ERRORE!

l'ultima istruzione è errata perché fa riferimento alla variabile t, definita all'interno del precedente blocco, che al di fuori di esso ha cessato di esistere.

if - else
Il costrutto condizionale più usato in Java è l'if, che può essere usato nelle due varianti con o senza else. Vediamo di ripassarli brevemente

if ( condizioneBooleana )
  istruzione;

esegue l'istruzione se la condizione booleana è vera, mentre prosegue senza fare niente in caso contrario. La variante con l'else ha una forma del tipo:

if ( condizioneBooleana )
  istruzione1;
else
  istruzione2;

e permette di specificare, oltre all'istruzione da eseguire in caso di successo, anche quella da eseguire in caso di fallimento. Come già spiegato nel paragrafo precedente, se vogliamo che venga eseguita più di una istruzione dobbiamo ricorrere ad un blocco:

if ( condizioneBooleana ) {
  istruzione1a;
  istruzione2a;
  istruzione3a;
}
else {
  istruzione1b;
  istruzione2b;
  istruzione3b;
}

 

if - else annidati
Il costrutto if può comparire anche all'interno di un altro costrutto if, creando strutture nidificate anche molto complesse: vediamo di analizzare alcuni esempi. Partiamo da un frammento di codice con due if concatenati:

if( x >= 0 )
  if( x <= 10 )
    System.out.println("x è compreso tra 0 e 10");

Il primo di questi dice "se la variabile x è maggiore o uguale a 0, esegui l'istruzione seguente"; l'istruzione successiva è a sua volta un if che dice "se la variabile x è minore o uguale a cento, esegui l'istruzione successiva": pertanto la terza istruzione verrà eseguita solamente se entrambe le condizioni precedenti risultano vere.

Se a questo punto inserisco un else dopo queste istruzioni, a quale dei due if farà riferimento? Nel linguaggio Java, un'istruzione else fa sempre riferimento all'ultimo if della catena (quello più interno). Per sottolineare il concetto, si usa allineare l'else al corrispondente if:

if( x >= 0 )
  if( x <= 10 )
    System.out.println("x è compreso tra 0 e 10");
  else
    System.out.println("x è maggiore di 10");

Se ora aggiungiamo un ulteriore else, esso farà riferimento al primo if

if( x >= 0 )
  if( x <= 10 )
    System.out.println("x è compreso tra 0 e 10");
  else // riprende l'istruzione if( x <= 10 )
    System.out.println("x è maggiore di 10");
else // riprende l'istruzione if( x >= 0 )
  System.out.println("x è minore di 0");

Come ci dobbiamo comportare se volessimo forzare un else a far riferimento ad un if esterno? Come possiamo rimuovere dall'esempio precedente il primo else, facendo in modo che quello che rimane faccia ancora riferimento all'if più esterno? Per ottenere questo effetto dobbiamo racchiudere l'if più interno in un blocco: in questo modo l'if interno verrà trattato come un'istruzione a se stante, priva di else:

if( x >= 0 ) {
  if( x <= 10 )
    System.out.println("x è compreso tra 0 e 10");
}
else // riprende l'istruzione if( x >= 0 )
  System.out.println("x è minore di 0");

E' buona norma di programmazione evitare di ricorrere pesantemente alla nidificazione di istruzioni if, data la confusione che spesso ne segue. La formulazione di combinazioni condizionali troppo complesse spesso lascia scappare degli errori che è poi molto difficile correggere. Con un pò di ragionamento, di non è difficile produrre una forma più leggibile ricorrendo agli operatori booleani. Ad esempio il frammento di codice

if( x >= 0 )
  if( x <= 10 )
    System.out.println("x è compreso tra 0 e 10");

può tranquillamente essere sostituito dal seguente, in tutto equivalente:

if( x >= 0 && x <= 10 )
  System.out.println("x è compreso tra 0 e 10");

 

if - else concatenati
Un caso più semplice di combinazione condizionale si ha quando si fa seguire ad un else una if. In questo caso, la verità di ogni if prescinde dalla verità di tutte le if precedenti, creando in tal modo una catena di alternative:

if( x <= 0 )
  System.out.println("x è minore o uguale a 0");
else if( x <= 10)
  System.out.println("x è maggiore di 0 e minore o uguale a 10");
  else if ( x <= 20)
    System.out.println("x è maggiore di 10 e minore uguale a 20");
  else
    System.out.println("x è maggiore di 20");

Si noti che in questo caso l'ultimo else comprende tutti i casi non considerati dalle precedenti istruzioni.

 

Il costrutto switch - case
Il costrutto switch permette di gestire tutte quelle situazioni in cui dobbiamo prendere scelte diverse a seconda del valore di un'espressione.

switch (espressione) {
  case val1:
    istruzione_1a;
    istruzione_2a;
    ....
    istruzione_na;
    break;
  case val2:
    istruzione_1b;
    istruzione_2b;
    ....
    istruzione_nb;
    break;
    ....
  default:
    istruzione_1default;
    istruzione_2defefault;
    ....
    istruzione_ndefault;
    break;
}

L'espressione contenuta tra le parentesi dello switch deve essere di tipo intero (int, byte, short o char); ogni istruzione case lavora su di un particolare valore, e fornisce una sequenza di istruzioni da eseguire in quella circostanza. Tale sequenza termina usualmente con l'istruzione break, che forza il computer a uscire dallo switch, senza verificare i valori sucessivi. Nonostante il break sia opzionale, il suo uso è fortemente consigliato.

Dopo aver specificato un numero qualunque di case, si può chiudere l'elenco specificando il blocco di default, ovvero una sequenza di istruzioni da eseguire se non si è verificato nessuno dei casi precedenti. Il blocco default è opzionale, e pertanto verrà inserito solamente nelle circostanze nelle quali risulti necessario.

Il seguente esempio stamperà uno specifico messaggio se x vale 1, 2 o 3, mentre stamperà un messaggio di default in tutti gli altri casi.

switch (x) {
  case 1:
    System.out.println("x è uguale a 1");
    break;
  case 2:
    System.out.println("x è uguale a 2");
    break;
  case 3:
    System.out.println("x è uguale a 3");
    break;
    default:
    System.out.println("x è diverso da 1, 2 e 3");
break;
}

Espressioni condizionali
L'operatore ? può essere usato all'interno di espressioni matematiche, dove una delle sotto espressioni sia vincolata ad una particolare condizione booleana.

espressioneBooleana ? espressione1 : espressione2;

Vediamo ad esempio un'istruzione che assegna alla variabile y il valore assoluto di x, ossia il valore x se x è positivo, -x se x è negativo:

y = x < 0 ? -x : x;

Ovviamente è sempre possibile costruire una forma equivalente ricorrendo ad un costrutto if-else:

if ( x < 0 )
y = -x;
else
y = x;

L'uso di una forma o di un'altra deve sempre prescindere da criteri di leggibilità del codice risultante.

 

Conclusioni
Questo mese abbiamo approfondito l'uso dei costrutti condizionali in Java, costrutti che permettono di vincolare l'esecuzione di un'istruzione ad una particolare condizione. Abbiamo inoltre approfondito l'uso delle condizioni booleane e introdotto l'uso dei blocchi. Il mese prossimo parleremo di costrutti iterativi.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it