OBIETTIVI DELLA LEZIONE
In questa lezione:
Ripasso: nomi identificatori
Java è un linguaggio "case-sensitive", cioè differenzia tra lettere minuscole e maiuscole (come il C ma diversamente dal Fortran, ad es.).
Attenzione: è un errore molto comune chiamare una variabile con un nome tutto minuscolo e referenziarla poi con una iniziale maiuscola!
int saldo; Saldo = ... // SBAGLIATO! saldo è tutto minuscolo
Un altro errore comune è quello di indicare il nome di un tipo primitivo con la maiuscola. Attenzione: i tipi primitivi non hanno iniziale maiuscola! Ma hanno iniziale maiuscola le pseudoclassi che contengono i metodi che agiscono sui tipi primitivi (sono pur sempre classi). Esempio chiarificatore:
Int numero; // SBAGLIATO, i tipi primitivi hanno nomi minuscoli Stringa input; // Giusto, le stringhe sono oggetti! numero = int.ParseInt(input); // Sbagliato, int è un tipo primitivo e non ha metodi! numero = Integer.ParseInt(input); // Giusto, Integer è una classe
Consideriamo il caso in cui si debba effettuare un assegnamento condizionale
if(x >= 0) y = 2; else y = -1;
Un alternativa efficace è l'utilizzo dell'operatore condizionale ternario ?:
y = (x>=0) ? 2 : -1;
La sintassi dell'opertore è espressioneCondizionale ? valoreSeTrue : valoreSeFalse
La seconda (e ultima) struttura condizionale che vediamo è il costrutto switch - case. E' un costrutto sopravvissuto all'evoluzione della specie e deriva direttamente dalle jump-table (goto) dei linguaggi di seconda generazione (assembly). E' sopravvissuto perché in alcuni casi è molto utile! Serve quando dobbiamo confrontare una variabile di tipo intero (assolutamente non boolean, né float, né double, né long) con una serie di possibilità. Tipico esempio: scelta multipla.
import javax.swing.*; class Multipla { public static void main(String[] args) { String stringaInput; int numero; char risposta; stringaInput = JOptionPane.showInputDialog("Scegli un numero"); numero = Integer.parseInt(stringaInput); stringaInput = JOptionPane.showInputDialog("Cosa vuoi farne?\n" + "Premi R per radice quadrata,\n" + " E per il quadrato,\n" + " C per il cubo e\n" + " n o N per uscire."); risposta = stringaInput.charAt(0); switch(risposta) { // risposta è un char ma viene // implicitamente trasformato in un int case 'R': // inizia qui se risposta == 'R' JOptionPane.showMessageDialog(null, "La radice quadrata è " + Math.sqrt(numero)); break; // finisce qui case 'E': // inizia qui se risposta == 'E' JOptionPane.showMessageDialog(null, "Il quadrato è " + Math.pow(numero,2.0)); break; // finisce qui case 'C': // inizia qui se risposta == 'E' JOptionPane.showMessageDialog(null, "Il cubo è " + Math.pow(numero,3.0)); break; // finisce qui case 'n': // inizia qui se risposta == 'n' case 'N': // inizia qui se risposta == 'N' JOptionPane.showMessageDialog(null, "Faccio come vuoi tu."); break; // finisce qui default: // in ogni altro caso inizia qui JOptionPane.showMessageDialog(null, "Non sai stare al gioco."); break; // finisce qui, ma il break non sarebbe stato necessario visto // che siamo alla fine del blocco e saremmo usciti comunque. } System.exit(0); // da non dimenticare! } }
Spesso è necessario ripetere numerose volte lo stesso blocco di istruzioni e tutti i linguaggi di programmazione propongono strutture di controllo per i cicli. In questo il Java assomiglia quasi completamente al C (ad onor del vero la versione 1.5 di Java introduce un nuovo tipo di ciclo, somigliante al foreach del PHP ma non presente in C).
Fondamentalmente, un ciclo è una struttura di controllo che esegue un blocco di istruzioni fino a che una condizione logica rimane vera.
Esempio: calcoliamo la somma dei numeri interi minori ed uguali del numero N (dall'algebra elementare sappiamo che la somma sarà pari a N*(N+1)/2). Per prima cosa costruiamoci un diagramma a blocchi:
Una possibile soluzione è la seguenteimport javax.swing.*; class SommaInteri { public static void main(String[] args) { String stringaInput; int numero, N, somma; stringaInput = JOptionPane.showInputDialog("Scegli il numero N"); N = Integer.parseInt(stringaInput); somma = 0; numero = 1; while(numero <= N) { somma += numero; numero++; } JOptionPane.showMessageDialog(null, "La somma è " + somma); } }
Se si vuole che la condizione logica sia valutata alla fine del ciclo, si può utilizzare il costrutto do-while
do { istruzioniDelCiclo } while(condizioneLogica);
Il costrutto for è, per quanto ci riguarda, assolutamente equivalente al while. Le parti fondamentali del ciclo sono raggruppate in un costrutto più sintetico: for(condizioneIniziale; condizioneLogica; incremento). La parte centrale dell'esempio precedente andrebbe riscritta come:
somma = 0; for(numero = 1; numero <= N; numero++) { somma += numero; }
In definitiva, i due costrutti seguenti sono equivalenti:
istrInizializzazione; while(condizioneLogica) { istruzione1; istruzione2; ... istrIncremento; } |
for(istrInizializzazione; condizioneLogica; istrIncremento) { istruzione1; istruzione2; ... } |
Prediligeremo il for quando il numero di iterazioni è prefissato, il while altrimenti. Ma si potrebbe anche usare sempre uno dei due costrutti...
Attenzione alla condizione di permanenza nel ciclo! Evitare se possibile test di uguaglianza su valori in virgola mobile!
break: permette di interrompere l’esecuzione del ciclo
continue: permettere di interrompere l’iterazione corrente
Entrambi gli operatori sono tipicamente utilizzate con una struttura condizionale. Le istruzioni non sono mai necessarie in un programma ed è meglio evitarle per migliorare la leggibilità.
Metodo = insieme di istruzioni raggruppate insieme per svolgere una determinata operazione. Perché utilizzare dei metodi:
modificatori tipoValoreRestituito nomeMetodo(tipo1 par1, tipo2 par2, ...) { istruzione1; istruzione2; ... return valore; }
Un po' di terminologia:
Per invocare un metodo:
Un esempio:
import javax.swing.*; class SommaInteriMetodo { public static int sommaFinoN(int numeroMassimo) { int numero, somma; somma = 0; numero = 1; while(numero <= numeroMassimo) { somma += numero; numero++; } return somma; } public static void main(String[] args) { String stringaInput; int N; stringaInput = JOptionPane.showInputDialog("Scegli il numero N"); N = Integer.parseInt(stringaInput); JOptionPane.showMessageDialog(null, "La somma è " + sommaFinoN(N)); } }
Abbiamo già utilizzato dei metodi. Non ve lo avevo fatto notare ma, sono di due tipologie:
In un linguaggio OO il centro del programma sono gli oggetti stessi. Gli oggetti possono ricevere o inviare messaggi: con un messaggio un oggetto può trasferire dati o chiedere ad un secondo oggetto di manifestare un comportamento. L'oggetto che riceve il messaggio manifesterà il comportamento tramite l'esecuzione del metodo corrispondente. La classe specifica quali metodi l’oggetto dispone.
Vediamo un esempio. Nel codice sopra per avere il primo carattere della stringa abbiamo scritto la seguente istruzione:
risposta = stringaInput.charAt(0);
Scomponiamola:
Per comprendere bene come funzionino i metodi in Java ed evitare errori significativi, iniziamo con un breve ripasso sulla gestione della memoria in C, e su come funziona il "call stack". Lo potete trovare in queste slides.
Anticipando quanto vedremo meglio la prossima lezione, le variabili di tipo primitivo e riferimento ad oggetto sono disposte sullo stack. Gli oggetti risiedono invece sullo, lo spazio tipicamente riservato per l'allocazione di memoria dinamica. Le variabili riferimento ad oggetto altro non sono che riferimenti (puntatori) ai relativi oggetti.
Dunque, supponiamo di invocare il metodo:
public static double Math.sqrt(double a);
Al momento dell’invocazione in Java:
Vediamolo per l'esecuzione della classe SommaInteriMetodo in modo semplificato.
1 | 2 | 3 | 4 | 5 | 6 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
Il passaggio parametri in Java è sempre per valore! Non esiste il passaggio parametro per indirizzo. Attenzione però che quando passate ad un metodo una variabile di tipo riferimento ad oggetto, quello che passate è il riferimento!
Il passaggio parametri per valore con tipi di dato primitivi è immediato da capire ma porta ad eventuali errori. Vediamone uno tipico:
class Scambia { public static void main(String[] args) { int primoNumero = 10, secondoNumero = 20; scambiaNumeriInteri(primoNumero, secondoNumero); System.out.println("primoNumero = " + primoNumero + " e secondoNumero = " + secondoNumero); } static void scambiaNumeriInteri(int a, int b) { int temp; temp = a; a = b; b = temp; // a e b sono variabili temporanee che qui smettono di esistere. } }
Quando il metodo di classe scambiaNumeriInteri è stato invocato, sono state create sullo stack due nuove variabili intere, a e b, in cui sono stati copiati primoNumero e secondoNumero. Al termine dell'esecuzione del metodo, le variabili vengono rimosse dallo stack e nessuna modifica si propaga nel metodo main.
main() è un metodo static che Viene invocato direttamente dalla JVM. DEVE essere specificato come public static void.
Abbiamo utilizzato:
System.out.println(3.0) System.out.println("Benvenuto") System.out.println(5)
Quella che abbiamo sfruttato senza saperlo è la possibilità che Java offre di "sovraccaricare" (to overload) un metodo. In pratica costruiamo metodi differenti con lo stesso nome, ma con una diversa lista di parametri formali. Il compilatore sceglie il metodo che corrisponde meglio ai parametri attuali scelti dall'utente allorquando utilizza il metodo. La scelta del metodo tra quelli disponibili avviene dunque durante la fase di compilazione (NON durante l'esecuzione!).
L'overload rende più leggibile il codice. Vediamo un esempio, un metodo che calcola il maggiore di due numeri:
public static int massimo(int a, int b) { int c = ( a >= b ? a : b ); return c; } public static double massimo(double a, double b) { double c = ( a >= b ? a : b ); return c; }
A secondo del tipo di parametri formali che passeremo al compilatore, esso sceglierà il metodo corrispondente. Talvolta il compilatore non riesce a scegliere, perché più metodi (o nessuno) si prestano al caso specifico: avremo un errore in compilazione!
Una variabile di tipo char può solo contenere una lettera. Per rappresentare una successione di lettere dobbiamo utilizzare una stringa. Una stringa NON è un tipo di dato primitivo e per contenerla serve un oggetto.
Abbiamo detto che gli oggetti risiedono sullo heap. Ad esempio nella seguente dichiarazione, l'oggetto stringa viene creato sullo heap, inoltre:
Ripasso: concatenamento stringhe
Per concatenare due stringhe dobbiamo utilizzare l'operatore +. Rivediamo alcuni esempi.
Esempio: abbiamo le tre variabili altezza, lunghezza e larghezza e vogliamo una produrre una stringa del tipo:
"Le dimensioni della scatola sono: 13 x 10 x 2 cm (L x W x H)"
Per ottenerla:
"Le dimensioni della scatola sono: " + lunghezza + " x " + larghezza + " x " + altezza + " cm (L x W x H)";
Per costruire una stringa che, stampata, stia su più righe, ad esempio come:
Questa è una stringa su due righe!
basta inserire la sequenza di escape '\n' nel punto opportuno, cioè:
"Questa è una stringa\nsu due righe!"
Altri metodi utili della classe String
NOTA BENE: se vogliamo confrontare due stringhe in una espressione logica NON dobbiamo usare l'operatore == ma il metodo equals(). Questo è valido in genere per qualunque oggetto! L'operatore == confronta il riferimento all'oggetto.
Gli "array" in Java sono degli oggetti. Un esempio,
un array di 10 double:
Per dichiarare una variabile (riferimento a) array si utilizza la sintassi
double[] movimenti;
Notate la posizione delle parentesi quadre. Per motivi storici (venire incontro alle abitudini dei programmatori C) è possibile anche la sintassi alternativa:
double movimenti[];
E' sconsigliata!
Per costruire l'array si utilizza la sintassi:
movimenti = new double[10];
Che cosa è "new"? È un operatore unario prefisso, con il quale richiediamo alla JVM di creare un oggetto (nello heap). In generale, per creare un array utilizzaremo la sintassi:
new OggettoOTipoPrimitivo[numeroDiElementi]
Le variabili di tipo primitivo contenute nell'array vengono inizializzate ai valori di default indicati nella tabella della lezione scorsa. Vi anticipo che le variabili di tipo riferimento ad oggetto vengono inizializzate a null. Vediamo quale è il processo di dichiarazione e creazione di un array da parte della JVM in questa slide.
Infine ecco un possibile codice che produce l'array della figura vista in precedenza:
class ArrayDiDouble { public static void main(String[] args) { double[] movimenti = {12.32, 0.3, 28.78, 12.45, 9.01, 1.16, 26.9, 18.04, 11.08, 5.39}; for(int i=0; i<movimenti.length; i++) System.out.println(movimenti[i]); } }
Osservazione: nell'esempio è presentato un metodo di creazione e inizializzazione alternativo che utilizza una sorta di costante letterale per l'array:
double[] movimenti = {12.32, 0.3, 28.78, 12.45, 9.01, 1.16, 26.9, 18.04, 11.08, 5.39};
Il costrutto è equivalente a:
double[] movimenti = new double[10]; movimenti[0] = 12.32; movimenti[1] = 0.3; movimenti[2] = 28.78; movimenti[3] = 12.45; ...
Come in C, gli elementi si manipolano utilizzando l’operatore [ ].
L’oggetto array contiene una variabile con la lunghezza (cioè il numero di elementi contenuti nell'array: movimenti.length
L'esistenza di proprietà di un oggetto è un concetto comune a tutti gli oggetti. Vengono chiamate in modi diversi: campi o variabili di istanza. Conterranno informazioni che caratterizzeranno gli oggetti stessi. Es: auto: numero di Km percorsi; vestito: taglia, etc.
Vediamo come utilizzare il campo length in questo esempio, che prevede che l'utente inserisca 10 dati:
import javax.swing.*; class ArrayDiDouble { public static void main(String[] args) { String stringaInput; double[] movimenti = new double[10]; for(int i=0; i<movimenti.length; i++) { stringaInput = JOptionPane.showInputDialog("Inserisci l'elemento numero " + i); movimenti[i] = Double.parseDouble(stringaInput); } for(int i=0; i<movimenti.length; i++) System.out.println(movimenti[i]); System.exit(0); } }
Algoritmi da sapere: Massimo di un array
Questo non è un corso di algoritmi, ma di sicuro è necessario imparare bene alcuni algoritmi basilari. Tra questi la ricerca del massimo (o del minimo) di un array. L'idea è semplice:
Esercizio 1 [Soluzione]
Scrivere un programma che chieda all'utente di inserire tre numeri double. Il programma deve scegliere il maggiore e stamparlo a video.
Esercizio 2 [Soluzione]
Un anno bisestile ha 366 giorni. Un anno è bisestile se è divisibile per 4 (es: 1980), se non è divisibile per 100 (il 1900 non era bisestile), se è divisibile per 400 (il 2000 era bisestile). Il calendario gregoriano è stato introdotto nel 1582 e prima di esso non esistevano gli anni bisestili. Scrivere un programma che chieda all'utente un anno e che risponda indicando se quell'anno sia (stato) bisestile o meno.
Esercizio 3 [Soluzione]
Riscrivere il seguente codice, sostituendo il ciclo for con uno while
int s = 0; for (int i = 1; i <= 10; i++) s = s + 1;
Esercizio 4 [Soluzione]
Scrivere un programma che stampi a video il seguente pattern:
1 22 333 4444 55555 666666 7777777 88888888 999999999
Esercizio 5 [Soluzione]
Scrivere un programma che stampi a video il seguente pattern:
######## # ####### ## ###### ### ##### #### #### ##### ### ###### ## ####### # ########
Esercizio 6 [Soluzione]
Scrivere un programma che chieda all'utente di inserire 7 numeri double e li metta in un array. Quindi il programma deve indicare all'utente quale era il numero maggiore.
©2008 Roberto Sassi