SETTIMANA 4
Problema
 Riprendiamo un problema visto nella prima lezione, per il quale abbiamo individuato un algoritmo senza realizzarlo
 Problema: Avendo depositato ventimila euro in un conto bancario che produce il 5% di interessi all’anno, capitalizzati annualmente, quanti anni occorrono affinché il saldo del conto arrivi al doppio della cifra iniziale?
 Abbiamo bisogno di un programma che ripeta degli enunciati (capitalizzazione degli interessi annuali, incremento del conto degli anni), finchè non si realizza la condizione desiderata
Iterazioni
(Capitolo 6)
1
Algoritmo che risolve il problema
2
Il ciclo while
 Sintassi: while (condizione)
enunciato
 Scopo:
 eseguire un enunciato finché la condizione è vera
 Il corpo del ciclo while può essere un enunciato qualsiasi, quindi anche un blocco di enunciati
 L’enunciato while realizza un ciclo
 per capire cosa significa questa espressione è utile osservare la rappresentazione del codice mediante diagrammi di flusso
2. All’anno 0 il saldo è 20000
3. Ripetere i passi 3 e 4 finché il saldo è minore del doppio di 20000, poi passare al punto 5
4. Aggiungere 1 al valore dell’anno corrente
5. Il nuovo saldo è il valore del saldo precedente moltiplicato per 1.05 (cioè aggiungiamo il 5%)
6. Il risultato è il valore dell’anno corrente
 L’enunciato while consente la realizzazione di programmi che devono eseguire ripetutamente una serie di azioni finché è verificata una condizione
3
Soluzione: classe Investment
4
Soluzione: classe InvestmentTester
public class Investment
{
public Investment(double aBalance, double aRate)
{ balance = aBalance;
rate = aRate;
years = 0;
}
public void waitForBalance(double targetBalance)
{ while (balance < targetBalance)
{ years++;
double interest = balance * rate / 100;
balance = balance + interest;
}
}
public double getBalance()
{ return balance; }
public int getYears()
{ return years; }
public class InvestmentTester
{
public static void main(String[] args)
{
final double INITIAL_BALANCE = 10000;
final double RATE = 5;
Investment invest
= new Investment(INITIAL_BALANCE, RATE);
invest.waitForBalance(2 * INITIAL_BALANCE);
int years = invest.getYears();
System.out.println("The investment doubled after "
+ years + " years");
}
}
private double balance;
private double rate;
private int years;
}
5
6
È tutto chiaro? …
1. Quante volte viene eseguito il seguente ciclo?
Cicli infiniti
 Esistono errori logici che impediscono la terminazione di un ciclo, generando un ciclo infinito
 l’esecuzione del programma continua ininterrottamente
 Bisogna arrestare il programma con un comando del sistema operativo, o addirittura riavviare il computer
int year = 0;
while (year < 20)
{ double interest = balance * rate / 100;
balance = balance + interest;
// qui manca year++
}
int year = 20;
while (year > 0)
{ year++; // doveva essere year-double interest = balance * rate / 100;
balance = balance + interest;
}
while (false)
// enunciato
2. Cosa succede nel programma InvestmentTester se RATE nel metodo main vale 0?
Ctrl + C
7
8
Ciclo for
 Molti cicli hanno questa forma
Cicli for
i = inizio;
while (i < fine)
{ enunciati
i++;
}
 Per comodità esiste il ciclo for equivalente
for (i = inizio; i < fine; i++)
{ enunciati
}
 Non è necessario che l’incremento sia di una sola unità, né che sia positivo, né che sia intero
for (double x = 2; x > 0; x = x - 0.3)
{ enunciati
}
9
10
L’enunciato for
Il metodo waitYears di Investment
 Sintassi:
 Vogliamo sapere quale sarà il saldo finale del nostro investimento dopo 20 anni, con tasso di interesse costante al 5%
for (inizializzazione; condizione; aggiornamento)
enunciato
public class Investment
{
...
public void waitYears(int n)
{
for (int i = 1; i <= n; i++)
{
double interest = balance * rate / 100;
balance = balance + interest;
}
years = years +n;
}
 Scopo: eseguire un’inizializzazione, poi ripetere l’esecuzione di un enunciato ed effettuare un aggiornamento finché la condizione è vera
 Nota: l’inizializzazione può contenere la definizione di una variabile, che sarà visibile soltanto all’interno del corpo del ciclo
for (int y = 1; y <= 10; y++)
{
…
}
// qui y non è più definita
...
11
}
12
Esempio: invertire una stringa
Visibilità delle variabili
 Se il valore finale di una variabile di controllo del ciclo deve essere visibile al di fuori del corpo del ciclo, bisogna definirla prima del ciclo
 Poiché una variabile definita nell’inizializzazione di un ciclo non è più definita dopo il corpo del ciclo, è possibile (e comodo) usare di nuovo lo stesso nome in altri cicli
public class ReverseTester
{ public static void main(String[] args)
{ Scanner in = new Scanner(System.in);
System.out.println("Inserire una stringa:");
String s = in.nextLine();
String r = "";
for (int i = 0; i < s.length(); i++)
{ char ch = s.charAt(i);
// aggiungi all’inizio
r = ch + r;
}
System.out.println(s + " invertita è " + r);
}
}
double b = ...;
for (int i = 1; i < 10 && b < c; i++)
{ ...
modifica b
}
// qui b è visibile, mentre i non lo è
for (int i = 3; i > 0; i--)
System.out.println(i);
13
14
È tutto chiaro? …
1. Riscrivere sotto forma di ciclo while il ciclo for del metodo waitYears
2. Quante volte viene eseguito il seguente ciclo for?
for (i = 0; i <= 10; i++)
System.out.println(i * i);
Cicli annidati
15
16
Problema
Problema
 Vogliamo stampare una tabella con i valori delle potenze xy, per ogni valore di x tra 1 e 4 e per ogni valore di y tra 1 e 5
 Per stampare la riga x­esima, bisogna calcolare e stampare i valori x1, x2, x3, x4 e x5, cosa che si può fare facilmente con un ciclo for
1
2
3
4
1
4
9
16
1
8
27
64
1
1
16
32
81 243
256 1024
// stampa la riga x-esima della tabella
for (int y = 1; y <= 5; y++)
{ int p = (int)Math.round(Math.pow(x, y));
System.out.print(p + " ");
}
System.out.println(); // va a capo
 Innanzitutto, bisogna stampare 4 righe
for (int x = 1; x <= 4; x++)
{ // stampa la riga x-esima della tabella
...
}
 Ogni iterazione del ciclo stampa un valore, seguito da uno spazio bianco per separare valori successivi; al termine si va a capo
17
18
Soluzione
Soluzione
 Mettendo insieme le due “soluzioni parziali” si risolve il problema, mediante due cicli “annidati” (nested) , cioè “uno dentro l’altro”
 Incolonnare dati di lunghezza variabile è un problema frequente
Ora ci sono final int COLUMN_WIDTH = 5;
tre cicli for (int x = 1; x <= 4; x++)
annidati!
for (int x = 1; x <= 4; x++)
{ // stampa la riga x-esima della tabella
for (int y = 1; y <= 5; y++)
{ // stampa il valore y-esimo
// della riga x-esima
int p = (int)Math.round(Math.pow(x, y));
System.out.print(p + " ");
}
System.out.println(); // va a capo
}
1
2
3
4
1 1 1 1
4 8 16 32
9 27 81 243
16 64 256 1024
Le colonne non sono allineate!
{
}
for (int y = 1; y <= 5; y++)
{ // converte in stringa il valore
String p = "" + (int)Math.round(Math.pow(x, y));
// aggiunge gli spazi necessari
while (p.length() < COLUMN_WIDTH)
p = " " + p;
System.out.print(p);
}
System.out.println();
1
1
1
1
1
2
4
8
16
32
3
9
27
81 243
4
16
64 256 1024
19
20
Il problema del “ciclo e mezzo”
 Un ciclo del tipo
 “fai qualcosa, verifica una condizione, fai qualcos’altro e ripeti il ciclo se la condizione era vera”
 non ha una struttura di controllo predefinita in Java e deve essere realizzata con un “trucco”, come quello di usare una variabile booleana, detta variabile di controllo del ciclo
Il problema del “ciclo e mezzo”
 Una struttura di questo tipo si chiama anche “ciclo e mezzo” o ciclo ridondante (perché c’è qualcosa di “aggiunto”, di innaturale…)
21
22
Ciclo e mezzo: il programma QEater
Il problema del “ciclo e mezzo”
import java.util.Scanner;
 Situazione tipica: l’utente deve inserire un insieme di valori, la cui dimensione non è predefinita
 Si realizza un ciclo while, dal quale si esce soltanto quando si verifica la condizione all’interno del ciclo
boolean done = false;
while (!done)
{ System.out.println(“Valore?”);
String input = in.next();
if (input.equalsIgnoreCase(“Q”))
done = true;
La lettera “Q” (Quit) è else
... // elabora line
un valore sentinella che }
segnala che l'immissione dei dati è terminata
23
public class QEater
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
boolean done = false;
while(!done)
{
System.out.println("Voglio una Q");
String input = in.nextLine();
if (input.equals("Q"))
{
System.out.println("Grazie!");
done = true;
}
else
{
System.out.println("Allora non hai capito...");
}
}
}
}
24
Break, continue, e codice spaghetti
while (true)
{ String input = in.next();
if (input.equalsIgnoreCase(“Q”))
break;
else
... // elabora line
}
Materiale di complemento
(Capitolo 7)
 Soluzione alternativa alla struttura “ciclo e mezzo”:  usare un ciclo infinito while(true) e l’enunciato break
 L’enunciato break provoca la terminazione del ciclo
 Esiste anche l'enunciato continue, che fa proseguire l'esecuzione dalla fine dell'iterazione attuale del ciclo
 L'uso di break e continue è non necessario e sconsigliabile
 perchè contribuisce a creare codice spaghetti  ovvero rappresentato da diagrammi di flusso pieni di linee, difficili da leggere e comprendere
25
26
Variabili non inizializzate
 È buona regola fornire sempre un valore di inizializzazione nella definizione di variabili
Variabili non inizializzate
int lit;
 Cosa succede altrimenti?
 la definizione di una variabile “crea” la variabile, cioè le riserva uno spazio nella memoria primaria (la quantità di spazio dipende dal tipo della variabile)
 tale spazio di memoria non è “vuoto”, una condizione che non si può verificare in un circuito elettronico, ma contiene un valore “casuale” (in realtà contiene l’ultimo valore attribuito a quello spazio da un precedente programma… valore che a noi non è noto)
27
28
Variabili non inizializzate
Variabili non inizializzate
 Se si usasse il valore di una variabile prima di averle assegnato un qualsiasi valore, il programma si troverebbe ad elaborare quel valore che “casualmente” si trova nello spazio di memoria riservato alla variabile
ERRORE
 Questo problema provoca insidiosi errori di esecuzione in molti linguaggi di programmazione
 il compilatore Java, invece, segnala come errore l’utilizzo di variabili a cui non sia mai stato assegnato un valore (mentre non è un errore la sola definizione...)
Coins6.java:5: variable lit might not have been initialized
public class Coins6 // NON FUNZIONA!
{ public static void main(String[] args)
{ int lit;
double euro = 2.35;
double totalEuro = euro + lit / 1936.27;
System.out.print("Valore totale in euro ");
System.out.println(totalEuro);
}
}
 questi errori non sono sintattici, bensì logici, ma vengono comunque individuati dal compilatore, perché si tratta di errori semantici (cioè di comportamento del programma) individuabili in modo automatico
29
30
Ciclo do
 Capita spesso di dover eseguire il corpo di un ciclo almeno una volta, per poi ripeterne l’esecuzione se è verificata una particolare condizione  Esempio tipico: leggere un valore in ingresso, ed eventualmente rileggerlo finché non viene introdotto un valore “valido”
Cicli do
31
32
Ciclo do
 Si può usare un ciclo while “innaturale”
// si usa un’inizializzazione "ingiustificata"
double rate = 0;
while (rate <= 0)
{ System.out.println("Inserire il tasso:");
rate = console.readDouble();
}
Cicli: errori e consigli
 ma per comodità esiste il ciclo do
double rate;
do
{ System.out.println("Inserire il tasso:");
rate = console.readDouble();
} while (rate <= 0);
33
Errori per scarto di uno
34
Cicli for di “cattivo gusto”
int years = 0;
// o forse int years =1; ???
while (balance < 2 * initialBalance)
{
// o forse balance <= 2 * initialBalance ???
years++;
double interest = balance * rate / 100;
balance = balance + interest;
}
System.out.println("Investimento raddoppiato dopo"
for (rate = 5; years-- > 0; System.out.println(balance))
{ enunciati
}
 È un ciclo for sintatticamente corretto
 ma contiene condizioni estranee, che producono risultati difficilmente comprensibili
for (int i = 1; i <= years; i++)
{ if (balance >= targetBalance)
i = i + 1;
else
...
}
+ years + " anni.");
 Come evitare questi errori per scarto di uno?
 Provare con alcuni semplici casi di prova
 Investimento iniziale 100 euro, tasso 50%
 L'intestazione è corretta
 ma il contatore viene modificato nel corpo del ciclo
• Allora years deve essere inizializzato a 0
 Tasso di interesse 100%
• Allora la condizione deve essere < e non <=
35
 Sono cicli for di cattivo gusto!
36
Punti e virgola mancanti o di troppo
sum = 0;
for (int i = 1; i <= 10; i++);
 C'è un “;” di troppo!
sum = sum + i;
 il corpo del ciclo è vuoto System.out.println(sum);
 L'istruzione di aggiornamento di sum viene eseguita una sola volta, dopo l'uscita dal ciclo
 E se n fosse negativo? for (int i = 1; i
 La condizione i!=n sarebbe sempre vera
 Molto meglio i<=n
!= n; i++)
for (i = a; i < b; i+=c)
// oppure
 Imparare a contare le iterazioni for (i = a; i <= b; i+=c)
 Il primo ciclo (asimmetrico) viene eseguito (b­a)/c volte
 Il secondo (simmetrico) viene eseguito (b­a)/c +1 volte
for (years = 1; (balance = balance + balance * rate / 100)
< targetBalance; years++) // ;
System.out.println(years);
 In questo caso tutte le operazioni necessarie sono nell'intestazione del ciclo
 Il corpo del ciclo deve essere vuoto
 Ma abbiamo dimenticato il “;” dopo l'intestazione
 Quindi l'istruzione di stampa viene considerata parte del corpo
Definire bene i limiti
for (i = 0; i <= s.length() - 1; i++)
37
 Due intestazioni // è corretta, ma è meglio questa:
 Sono equivalenti for (i = 0; i < s.length(); i++)
 Nella prima i limiti sono simmetrici
 Nella seconda sono asimmetrici ma il codice è più leggibile 38
Progettazione di classi
(capitolo 8)
(capitolo 3 – sez. 3.7 e 3.8)
39
40
I parametri dei metodi
public void deposit(double amount)
{ balance = balance + amount;
}
Parametri espliciti/impliciti
Il parametro this
 Cosa succede quando invochiamo il metodo?
account.deposit(500);
 L’esecuzione del metodo dipende da due valori
 il riferimento all’oggetto account
 il valore 500
 Quando viene eseguito il metodo, il suo parametro esplicito amount assume il valore 500
 esplicito perché compare nella firma del metodo
41
42
I parametri dei metodi
Il parametro implicito dei metodi
public void deposit(double amount)
{ balance = balance + amount;
}
momsavings.deposit(500);
 All’interno di ogni metodo il riferimento all’oggetto con cui viene eseguito il metodo si chiama parametro implicito e si indica con la parola chiave this
 in questo caso, this assume il valore di account all’interno del metodo deposit
 Ogni metodo ha sempre uno ed un solo parametro implicito, dello stesso tipo della classe a cui appartiene il metodo
 eccezione: i metodi statici non hanno parametro implicito
 Il parametro implicito non deve essere dichiarato e si chiama sempre this
 Abbiamo già detto che un metodo può avere parametri espliciti e/o impliciti
 amount è il parametro esplicito del metodo
 balance si riferisce alla variabile di esemplare balance della classe BankAccount, ma sappiamo che di tale variabile esiste una copia per ogni oggetto
 Alla variabile balance di quale oggetto si riferisce il metodo?
 si riferisce alla variabile che appartiene all’oggetto con cui viene invocato il metodo, ma come fa?
43
44
È tutto chiaro? …
1. Quanti e quali sono i parametri impliciti ed Uso del parametro implicito
 La vera sintassi del metodo dovrebbe essere
espliciti del metodo withdraw di BankAccount? Quali sono i loro nomi e tipi?
2. Qual è il significato di this.amount nel metodo deposit? Oppure, se l’espressione è priva di significato, per quale motivo lo è?
public void deposit(double amount)
{ this.balance = this.balance + amount;
}
// this è di tipo BankAccount
 Ma Java consente una comoda scorciatoia:
 se un metodo si riferisce ad un campo di esemplare, il compilatore costruisce automaticamente un riferimento al campo di esemplare dell’oggetto rappresentato dal parametro implicito this
45
46
Classi “di utilità” e metodi statici
 Esistono classi che non servono a creare oggetti ma contengono metodi statici e costanti.
 Queste si chiamano solitamente classi di utilità
 La classe Math è un esempio di questo tipo di classi
 La classe Numeric scritta da noi è un altro esempio
 Esempio: una classe Financial
Membri di classe “statici”
public class Financial
{
public static double percentOf(double p, double a)
{ return (p / 100) * a; }
// qui si possono aggiungere altri metodi finanziari
}
 Non è necessario creare oggetti di tipo Financial per usare i metodi della classe:
double tax = Financial.percentOf(taxRate, total);
47
48
Variabili statiche
Soluzione
 Vogliamo modificare BankAccount in modo che
 il suo stato contenga anche un numero di conto
 Prima idea (che non funziona…)
 usiamo una variabile per memorizzare l’ultimo numero di conto assegnato
public class BankAccount
{ ...
private int accountNumber;
}
public class BankAccount
{ ...
private int accountNumber;
private int lastAssignedNumber;
...
public BankAccount()
{ lastAssignedNumber++;
accountNumber = lastAssignedNumber;
}
}
 il numero di conto sia assegnato dal costruttore
• ogni conto deve avere un numero diverso
• i numeri assegnati devono essere progressivi, iniziando da 1
49
Soluzione
50
Variabili statiche
public class BankAccount
{ ...
private int accountNumber;
private int lastAssignedNumber;
...
public BankAccount()
{ lastAssignedNumber++;
accountNumber = lastAssignedNumber;
}
}
 Ci serve una variabile condivisa da tutti gli oggetti della classe
 una variabile con questa semantica si ottiene con la dichiarazione static
public class BankAccount
{ ...
private static int lastAssignedNumber;
}
 Questo costruttore non funziona perché la variabile lastAssignedNumber è una variabile di esemplare
 ne esiste una copia per ogni oggetto
 il risultato è che tutti i conti creati hanno il numero di conto uguale a 1
 Una variabile static (variabile di classe) è condivisa da tutti gli oggetti della classe
 Ne esiste un’unica copia indipendentemente da quanti oggetti siano stati creati (anche zero)
51
Variabili statiche
52
Variabili statiche
 Ora il costruttore funziona
 Le variabili statiche non possono (da un punto di vista logico) essere inizializzate nei costruttori
 Il loro valore verrebbe inizializzato di nuovo ogni volta che si costruisce un oggetto, perdendo il vantaggio di avere una variabile condivisa!
 Bisogna inizializzarle quando si dichiarano
public class BankAccount
{ ...
private int accountNumber;
private static int lastAssignedNumber = 0;
...
public BankAccount()
{ lastAssignedNumber++;
accountNumber = lastAssignedNumber;
}
}
private static int lastAssignedNumber = 0;
 Questa sintassi si può usare anche per le variabili di esemplare, anziché usare un costruttore
 non è una buona pratica di programmazione
 Ogni metodo (o costruttore) di una classe può accedere alle variabili statiche della classe e modificarle
53
54
Variabili statiche
Variabili statiche
 Nella programmazione ad oggetti, l’utilizzo di variabili statiche deve essere limitato
 metodi che leggono variabili statiche ed agiscono di conseguenza, hanno un comportamento che non dipende soltanto dai loro parametri (implicito ed espliciti)
 In ogni caso, le variabili statiche devono essere private, per evitare accessi indesiderati
 È invece pratica comune (senza controindicazioni) usare costanti statiche, come nella classe Math
public class Math
{ ...
public static final double PI =
3.14159265358979323846;
}
 Tali costanti sono di norma public e per ottenere il loro valore si usa il nome della classe seguito dal punto e dal nome della costante, Math.PI
55
56
È tutto chiaro? …
1. Citare due variabili statiche della classe System
Categorie di variabili (sez. 3.7)
Ciclo di vita di una variabile
57
58
Ciclo di vita di una variabile
Ciclo di vita di una variabile
 Una variabile locale
 viene creata quando viene eseguito l’enunciato in cui è definita
 viene eliminata quando l’esecuzione del programma esce dal blocco di enunciati in cui la variabile è definita
 Sappiamo che in Java esistono quattro diversi tipi di variabili
 variabili locali (all’interno di un metodo)
 variabili parametro (dette parametri formali)
 variabili di esemplare o di istanza
 variabili statiche o di classe
 Hanno in comune il fatto di contenere valori appartenenti ad un tipo ben preciso.
 Differiscono per quanto riguarda il loro ciclo di vita
 cioè l’intervallo di tempo in cui, dopo essere state create, continuano ad occupare lo spazio in memoria riservato loro
• se non è definita all’interno di un blocco di enunciati, viene eliminata quando l’esecuzione del programma esce dal metodo in cui la variabile viene definita
 Una variabile parametro (formale)
 viene creata quando viene invocato il metodo
 viene eliminata quando l’esecuzione del metodo termina
59
60
Ciclo di vita di una variabile
Eliminazione di oggetti
 Una variabile statica
 viene creata quando la macchina virtuale Java carica la classe per la prima volta  viene eliminata quando la classe viene scaricata dalla macchina virtuale Java
 ai fini pratici, possiamo dire che esiste sempre...
 Una variabile di esemplare
 viene creata quando viene creato l’oggetto a cui appartiene
 viene eliminata quando l’oggetto viene eliminato
 Un oggetto è a tutti gli effetti “inutilizzabile” quando non esiste più nessun riferimento ad esso
 Se un programma abbandona molti oggetti in memoria, puo’ esaurire la memoria a disposizione
 La JVM effettua automaticamente la gestione della memoria durante l’esecuzione di un programma
 Usa un meccanismo di garbage collection
• Viene periodicamente riciclata, cioè resa di nuovo libera, la memoria eventualmente occupata da oggetti che non abbiano piu’ un riferimento nel programma
61
62
Modello della memoria in java
Allocazione della memoria in java
 Al momento dell’esecuzione, a ciascun programma java viene assegnata un’area di memoria
 Una parte della memoria serve per memorizzare il codice; quest’area è statica, ovvero non modifica le sue dimensioni durante l’esecuzione del programma
 La Java Stack è un’area dinamica (ovvero che cambia dimensione durante l’esecuzione) in cui vengono memorizzate i parametri e le variabili locali dei metodi
• Stack significa pila
 L'istruzione BankAccount b = new BankAccount();
 Produce questo effetto in memoria:
 La Java Heap è un’altra area dinamica in cui vengono creati oggetti durante l’esecuzione dei metodi di un programma, usando lo speciale operatore new
• Heap significa cumulo, mucchio 63
64
Visibilità di variabili locali
 Per evitare conflitti, dobbiamo conoscere l’ambito di visibilità di ogni tipo di variabile
 Ovvero la porzione del programma all’interno della quale si può accedere ad essa
 Esempio: due variabili locali con lo stesso nome
 Funziona perché gli ambiti di visibilità sono disgiunti
Ambiti di visibilità di variabili
65
public class RectangleTester
{
public static double area(Rectangle rect)
{ double r = rect.getWidth() * rect.getHeight();
return r; }
public static void main(String[] args)
{ Rectangle r = new Rectangle(5, 10, 20, 30);
double a = area(r);
System.out.println(r); }
}
66
Visibilità di variabili locali
Visibilità di membri di classe
 Anche qui gli ambiti di visibilità sono disgiunti:
 Membri private hanno visibilità di classe
 Qualsiasi metodo di una classe può accedere a variabili e metodi della stessa classe
 Membri public hanno visibilità al di fuori della classe
 A patto di renderne qualificato il nome, ovvero:
 Specificare il nome della classe per membri static
if (x >= 0)
{ double r = Math.sqrt(x);
. . . } // la visibilitò di r termina qui
else
{ Rectangle r = new Rectangle(5, 10, 20, 30);
// OK, questa è un’altra variabile r
. . . }
 Invece l’ambito di visibilità di una variabile non può contenere la definizione di un’altra variabile locale con lo stesso nome:
• Math.PI, Math.sqrt(x)
 Specificare l’oggetto per membri non static
• account.getBalance()
 Non è necessario qualificare i membri appartenenti ad una stessa classe
 Perché ci si riferisce automaticamente al parametro implicito this
Rectangle r = new Rectangle(5, 10, 20, 30);
if (x >= 0)
{ double r = Math.sqrt(x);
// Errore: non si può dichiarare un’altra var. r qui
. . . }
67
68
Visibilità di membri di classe
Visibilità sovrapposte
 Esempio: qualifica sottintesa dal parametro implicito
 Purtroppo gli ambiti di visibilità di una variabile locale e di una variabile di esemplare possono sovrapporsi
public class BankAccount
{
public void transfer(double amount, BankAccount other)
{ withdraw(amount); // cioè this.withdraw(amount);
other.deposit(amount);
}
public void withdraw(double amount)
{ if (balance > amount) // cioè this.balance
balance = balance – OVERDRAFT_FEE;
//cioè BankAccount.OVERDRAFT_FEE
else ...
}
...
private static double OVERDRAFT_FEE = 5;
...
}
public class Coin
{
...
public double getExchangeValue(double exchangeRate)
{
double value; // Variabile locale
...
return value;
}
private String name;
private double value; // Campo di esemplare omonimo
}
 Viene segnalato un errore in compilazione?
69
70
Visibilità sovrapposte
Visibilità sovrapposte
 Non viene segnalato alcun errore in compilazione
 Java specifica che in casi come questo prevale il nome della variabile locale
 La variabile di esemplare viene “messa in ombra” (shadowed)
 Questa scelta è giustificata dal fatto che la variabile di esemplare può sempre essere qualificata usando il parametro this
 Errore molto comune:
 utilizzare accidentalmente lo stesso nome per una variabile locale e un campo di esemplare
 Un analogo effetto di shadowing è prodotto da una variabile locale su una variabile statica omonima
 La variabile statica deve essere qualificata con il nome della classe
71
public class Coin
{
...
public Coin(double aValue, String aName)
{
value = aValue;
String name = aName; //ahi: abbiamo dichiarato una
nuova variabile name
}
...
private String name;
private double value;
}
72
È tutto chiaro? …
1. Qual è l’ambito di visibilità delle variabili amount e newBalance del metodo deposit di BankAccount?
2. Qual è l’ambito di visibilità del campo di esemplare balance di BankAccount?
Chiamate per valore e riferimento
73
Chiamate per valore e riferimento
74
Chiamate per valore e riferimento
 Un nuovo metodo transfer per la classe BankAccount:
void transfer(double amount, double otherBalance)
{
balance = balance - amount;
otherBalance = otherBalance + amount;
}
 Proviamo ad usarlo:
double savingsBalance = 1000;
harrysChecking.transfer(500, savingsBalance);
System.out.println(savingsBalance);
 Non funziona!
 All’inizio dell’esecuzione del metodo la variabile parametro otherBalance ha il valore di savingsBalance
 La modifica del valore di otherBalance non ha effetto su savingsBalance perché sono variabili diverse
 Alla fine dell’esecuzione otherBalance non esiste più 75
Chiamate per valore e riferimento
1000
76
Chiamate per valore e riferimento
 A volte si dice impropriamente che in Java i numeri sono passati per valore e gli oggetti per riferimento
 In realtà in Java il passaggio è sempre “per valore”
 il valore del parametro effettivo è assegnato al parametro formale, ma
 passando per valore una variabile oggetto, si passa una copia del riferimento in essa contenuto
 l’effetto “pratico” di questo passaggio è la possibilità di modificare lo stato dell’oggetto stesso, come avviene con il passaggio “per riferimento”
 Altri linguaggi (come C++) consentono il passaggio dei parametri “per riferimento”, rendendo possibile la modifica dei parametri effettivi
77
78
Materiale di complemento
(Capitoli 3/8)
Errori tipici con i costruttori
79
Invocare metodi senza oggetto
80
Invocare metodi senza oggetto
 Ci sono, però, casi in cui è ammesso (ed è pratica molto comune!) invocare metodi non statici senza specificare un riferimento ad oggetto
 Questo accade quando si invoca un metodo non statico di una classe da un altro metodo non statico della stessa classe
public class Program
{ public static void main(String[] args)
{ BankAccount account = new BankAccount();
deposit(500); // ERRORE
}
}
 viene usato il parametro implicito this
 In questo caso il compilatore non è in grado di compilare la classe Program, perché non sa a quale oggetto applicare il metodo deposit, che non è un metodo statico e quindi richiede sempre un riferimento ad un oggetto da usare come parametro implicito
public void withdraw(double amount)
{ balance = getBalance() - amount;
}
this.getBalance()
81
82
Tentare di ricostruire un oggetto
Tentare di ricostruire un oggetto
 A volte viene la tentazione di invocare un costruttore su un oggetto già costruito con l’obiettivo di riportarlo alle condizioni iniziali
 Un costruttore crea un nuovo oggetto con prefissate condizioni iniziali
 un costruttore può essere invocato soltanto con l’operatore new
 La soluzione è semplice
 assegnare un nuovo oggetto alla variabile oggetto che contiene l’oggetto che si vuole “ricostruire”
BankAccount account = new BankAccount();
account.deposit(500);
account.BankAccount(); // NON FUNZIONA!
cannot resolve symbol
symbol : method BankAccount ( )
location : class BankAccount
BankAccount account = new BankAccount();
account.deposit(500);
account = new BankAccount();
 Il messaggio d’errore è un po’ strano… il compilatore cerca un metodo BankAccount, non un costruttore, e naturalmente non lo trova!
83
84
Coesione e accoppiamento
Progettare classi: accoppiamento, coesione, effetti collaterali
(sezioni 8.2, 8.4)
 Una classe dovrebbe rappresentare un singolo concetto
 Metodi pubblici e costanti elencati nell’interfaccia pubblica devono avere una buona coesione
 Ovvero devono essere strettamente correlate al singolo concetto rappresentato dalla classe
 Quando una classe usa un'altra classe all'interno del proprio codice si parla di accoppiamento tra le due classi
 Visualizziamo accoppiamento con diagrammi UML (Unified Modeling Language)
 La linea tratteggiata con freccia aperta punta alla classe nei cui confronti esiste dipendenza
85
Accoppiamento
86
Effetti collaterali
 Problema: se una classe viene modificata, tutte quelle che da essa dipendono possono richiedere modifiche
 Problema: per usare una classe in un programma, vanno usate anche tutte quelle da cui essa dipende
 Bisogna eliminare tutti gli accoppiamenti non necessari
 Effetto collaterale di un metodo:
 Qualsiasi tipo di comportamento osservabile al di fuori del metodo stesso
 Qualsiasi metodo modificatore ha effetti collaterali
 Modificano il proprio parametro esplicito
 Esistono altri tipi di effetto collaterale:
 Modifica di un parametro esplicito
 Esempio: un metodo transfer per BankAccount
87
public void transfer(double amount, BankAccount other)
{
balance = balance - amount;
other.balance = other.balance + amount;
// modifica il parametro esplicito
}
88
È tutto chiaro? …
1. Se a è un riferimento ad un oggetto Effetti collaterali
 Un altro tipo di effetto collaterale: visualizzazione di dati in uscita
 Esempio: un metodo printPurchase per CashRegister
BankAccount, allora a.deposit(100) modifica l’oggetto. È un effetto collaterale?
2. Immaginiamo di avere scritto il seguente metodo in una classe Dataset: produce effetti collaterali?
public void printPurchase() // Non consigliato
{
System.out.println("The purchase is now $" + purchase);
}
 Problema: abbiamo ipotizzato che qualsiasi utente parli la lingua inglese
 Problema: abbiamo realizzato accoppiamento con le classi System e PrintStream
void read(Scanner in)
{ while (in.hasNextDouble())
add(in.nextDouble);
}
 Evitiamo di inserire attività di input/output all’interno di metodi di una classe che ha altre finalità
89
90
I pacchetti di classi (package)
 Tutte le classi della libreria standard sono raccolte in pacchetti (package) e sono organizzate per argomento e/o per finalità
 Esempio: la classe Rectangle appartiene al pacchetto java.awt (Abstract Window Toolkit)
 Per usare una classe di una libreria, bisogna importarla nel programma, usando l’enunciato
 import nomePacchetto.NomeClasse
 Le classi System e String appartengono al pacchetto java.lang
 il pacchetto java.lang viene importato automaticamente
Pacchetti
91
92
Stili per l’importazione di classi
Importare classi da un pacchetto
 Un enunciato import per ogni classe importata
 Sintassi:
import nomePacchetto.NomeClasse;
 Scopo: importare una classe da un pacchetto, per poterla utilizzare in un programma
import java.math.BigInteger;
import java.math.BigDecimal;
 Un enunciato import per tutte le classi di un pacchetto
 non è un errore importare classi che non si usano!
 Sintassi: import nomePacchetto.*;
 Scopo: importare tutte le classi di un pacchetto, per poterle utilizzare in un programma
import java.math.*;
 Se si usano più enunciati di questo tipo
 non è chiaro il pacchetto di appartenenza delle classi
 Ci possono essere classi omonime in pacchetti diversi
 Nota: le classi del pacchetto java.lang non hanno bisogno di essere importate
 Attenzione: non si possono importare più pacchetti con un solo enunciato
import java.util.*;
import javax.swing.*;
 La classe Timer, appartiene ad entrambi i pacchetti
• Se si definisce una variabile di tipo Timer il compilatore segnala un errore di “riferimento ambiguo”
import java.*.*; // ERRORE
93
94
95
96
Stili per l’importazione di classi
 È possibile non usare per nulla gli enunciati import, ed indicare sempre il nome completo delle classi utilizzate nel codice
java.math.BigInteger a =
new java.math.BigInteger("123456789");
 Questo stile è assai poco usato, perché è molto noioso, aumenta la probabilità di errori di battitura e aumenta la lunghezza delle linee del codice (diminuendo così la leggibilità del programma)
Argomenti inattesi (precondizoni)
 Spesso un metodo richiede che i suoi argomenti
 siano di un tipo ben definito
La gestione delle eccezioni
(capitolo 11)
• questo viene garantito dal compilatore
 abbiano un valore che rispetti certi vincoli, ad esempio sia un numero positivo
• in questo caso il compilatore non aiuta...
 Come deve reagire il metodo se riceve un parametro che non rispetta i requisiti richiesti (chiamati precondizioni)?
97
Argomenti inattesi (precondizioni)
98
Argomenti inattesi (precondizioni)
 Ci sono quattro modi per reagire ad argomenti inattesi
 non fare niente
 Terminare il programma con System.exit(1)
• questo è possibile soltanto in programmi non professionali
• La terminazione di un programma attraverso il metodo statico System.exit() non fornisce un meccanismo standard per informare dell’avvenuto
• il metodo termina la propria esecuzione senza segnalare errori. In questo caso la documentazione del metodo deve indicare chiaramente le precondizioni. La responsabilità di ottemperare alle precondizioni e’ del metodo chiamante.
• Eseguire solo se le precondizioni sono soddisfatte:
System.exit(0) invece termina l’esecuzione segnalando che tutto è andato bene
if (precondizioni) {... corpo del metodo ...}
 Usare una asserzione
– questo però si può fare solo per metodi con valore di ritorno void, altrimenti che cosa restituisce il metodo?
– Se restituisce un valore casuale senza segnalare un errore, chi ha invocato il metodo probabilmente andrà incontro a un errore logico
• Il meccanismo delle “asserzioni” si usa solo per i programmi in fase di sviluppo e collaudo. • Noi non lo esaminiamo
 Lanciare un’eccezione
99
Lanciare una eccezione
Enunciato throw
 Il meccanismo generale di segnalazione di errori in  Sintassi:
Java consiste nel “lanciare” (throw) un’eccezione
 si parla anche di sollevare o generare un’eccezione
 Lanciare un’eccezione in risposta ad un parametro che non rispetta una precondizione è la soluzione più corretta in ambito professionale
 la libreria standard mette a disposizione tale eccezione: IllegalArgumentException
public void deposit(double amount)
{ if (amount <= 0)
throw new IllegalArgumentException();
balance = balance + amount;
}
100
throw oggettoEccezione;
 Scopo: lanciare un’eccezione
 Nota:
 di solito l’oggettoEccezione viene creato con new ClasseEccezione( )
 Non è necessario memorizzare il riferimento in una variabile oggetto
101
102
Le eccezioni in Java
 Quando un metodo lancia un’eccezione
 l’esecuzione del metodo viene interrotta
 l’eccezione viene “propagata” al metodo chiamante, la cui esecuzione viene a sua volta interrotta
 l’eccezione viene via via propagata fino al metodo main, la cui interruzione provoca l’arresto anormale del programma con la segnalazione dell’eccezione che è stata la causa di tale terminazione prematura
 Il lancio di un’eccezione è quindi un modo per terminare un programma in caso di errore
 non sempre, però, gli errori sono così gravi...
Gestire le eccezioni
 Esaminiamo un problema tipico:
 convertire stringhe in numeri
 Supponiamo che la stringa sia stata introdotta dall’utente
 se la stringa non contiene un numero valido, viene generata un’eccezione NumberFormatException
 sarebbe interessante poter gestire tale eccezione, segnalando l’errore all’utente e chiedendo di inserire nuovamente il dato numerico, anziché terminare prematuramente il programma
 Possiamo intercettare l’eccezione e gestirla con il costrutto sintattico try/catch
103
Gestire le eccezioni
104
Gestire le eccezioni
 L’enunciato che contiene l’invocazione del metodo che può generare l’eccezione deve essere racchiuso tra due parentesi graffe, precedute dalla parola chiave try
 Il blocco try è seguito da una clausola catch, definita in modo simile ad un metodo che riceve un solo parametro, del tipo dell’eccezione che si vuole gestire
try {
...
n = Integer.parseInt(line);
...
}
catch (NumberFormatException e)
{ System.out.println("messaggio");
}
 Nel blocco catch si trova il codice che deve essere eseguito nel caso in cui si verifichi l’eccezione
 l’esecuzione del blocco try viene interrotta nel punto in cui si verifica l’eccezione e non viene più ripresa
 Bisogna poi sapere di che tipo è l’eccezione generata dal metodo
 nel nostro caso NumberFormatException
105
Esempio: gestire NumberFormatException
import java.util.Scanner;
public class IntEater
{
public static void main(String[] args)
{
System.out.println("Passami un numero int!");
Scanner in = new Scanner(System.in);
int n = 0;
boolean done = false;
while (!done)
{ try{ String line = in.nextLine();
n = Integer.parseInt(line);
// l'assegnazione seguente e` eseguita
// solo se NON viene lanciata l'eccezione
done = true; }
catch(NumberFormatException e)
{ System.out.println("No, voglio un int");
// all'uscita del blocco catch done e` false
}
}
System.out.println("Grazie, " + n + " e` un numero int!");
}
}
106
Enunciati try/catch
 Sintassi:
try
{ enunciatiCheForseGeneranoUnaEccezione
}
catch (ClasseEccezione oggettoEccezione)
{ enunciatiEseguitiInCasoDiEccezione
}
 Scopo: eseguire enunciati che possono generare una eccezione
 se si verifica l’eccezione di tipo ClasseEccezione, eseguire gli enunciati contenuti nella clausola catch
 altrimenti, ignorare la clausola catch
107
108
Perché usare eccezioni?
Enunciati try/catch
 Idea generale: per gestire un errore di esecuzione ci sono due compiti da svolgere  Individuazione
 Ripristino
 Problema: il punto in cui viene individuato l’errore di solito non coincide con il punto di ripristino
 Esempio: il metodo parseInt fallisce (ovvero la stringa in esame non può essere convertita in intero)
 Il metodo parseInt non sa come gestire questo fallimento: dipende dal contesto!
 Soluzione: il meccanismo di lancio/cattura di eccezioni
 Consente di gestire un errore di esecuzione in un punto diverso rispetto a dove questo si è generato
 Obbliga a gestire l'errore, altrimenti l'esecuzione termina
 Se gli enunciati nel blocco try possono generare più eccezioni di diverso tipo, è necessario prevedere un blocco catch per ogni tipo di eccezione
try
{ enunciatiCheForseGeneranoPiu’Eccezioni
}
catch (ClasseEccezione1 oggettoEccezione1)
{ enunciatiEseguitiInCasoDiEccezione1
}
catch (ClasseEccezione2 oggettoEccezione2)
{ enunciatiEseguitiInCasoDiEccezione2
}
109
110
Tipi di eccezioni
Come gestire le eccezioni?
 Lanciare presto, catturare tardi
 Quando un metodo incontra un problema che non è in grado di gestire al meglio
 Conviene lanciare un’eccezione piuttosto che mettere in atto una soluzione imprecisa/incompleta
 “Lanciare presto”
 Quando un metodo riceve un’eccezione lanciata da un altro metodo
 Conviene catturarla (catch) solo se si è effettivamente in grado di risolvere il problema in maniera competente
 Altrimenti è meglio lasciare propagare l’eccezione (eventualmente fino al metodo main in esecuzione)
 “Catturare tardi”
111
112
Eccezioni controllate e non
Diversi tipi di eccezioni
 In Java esistono diversi tipi di eccezioni (cioè diverse classi di cui le eccezioni sono esemplari)
 eccezioni di tipo Error
 eccezioni di tipo Exception
• un sottoinsieme sono di tipo RuntimeException
 Eccezioni di tipo Error e di tipo RuntimeException sono eccezioni non controllate
 La loro gestione è facoltativa, ovvero lasciata alla scelta del programmatore
 se non vengono gestite e vengono lanciate, provocano la terminazione del programma
 Le altre sono eccezioni controllate
 la loro gestione è obbligatoria
113
 Le eccezioni controllate
 Descrivono problemi che possono verificarsi prima o poi, indipendentemente dalla bravura dell programmatore
• Per questo motivo le eccezioni di tipo IOException sono controllate
• Se si invoca un metodo che può lanciare un’eccezione controllata, è obbligatorio gestirla con try/catch
• Altrimenti viene segnalato un errore in compilazione
 Le eccezioni non controllate
 Descrivono problemi dovuti ad errori del programmatore e che quindi non dovrebbero verificarsi (in teoria…)
• Per questo motivo le eccezioni di tipo RuntimeException sono non controllate
• Non è obbligatorio catturarle tramite try/catch
114
Gestire le eccezioni di Input/Output
 Scanner può essere usata anche per leggere file (lo vedremo prossimamente); in questo caso il parametro esplicito e’ un oggetto di classe FileReader
 Se non trova il file, il costruttore FileReader() lancia l’eccezione controllata java.io.FileNotFoundException
Eccezioni controllate
import java.io.FileNotFoundException;
// QUESTO CODICE
import java.io.FileReader;
// NON COMPILA!
import java.util.Scanner;
public class MyInputReader
{
public static void main(String[] args)
{ String filename = “filename.txt”;
FileReader reader = new FileReader(filename);
Scanner in = new Scanner(reader);
}
MyInputReader.java:9:
}
115
Perché le eccezioni di IO sono controllate?
 Perchè sono associate a problemi che si possono verificare indipendentemente dalla bravura del programmatore
 Quando si leggono dati da un flusso di input
 se l’input proviene da un file su disco, il disco può essere difettoso
 se l’input proviene da una connessione di rete, la connessione può interrompersi durante la lettura
 se l’input proviene dalla tastiera, l'utente può inserire informazioni non corrette (ad esempio può non inserire input)
unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown
Gestire le eccezioni di IO
 Ci sono varie strategie per gestire eccezioni di IO
 Il modo più semplice
 intercettiamo (catch) l’eccezione quando si verifica, usando un blocco try con una clausola catch, e terminiamo l’esecuzione del programma con una segnalazione d’errore
try {
String name = buffer.readLine();
}
catch (IOException e)
{ System.out.println(e);
System.exit(1);
}
117
Gestire eccezioni di IO
System.out.println(e);
116
catch (IOException e)
{ System.out.println(e);
System.exit(1);
}
 Se viene generata l’eccezione, il programma stampa sull’output standard la descrizione testuale standard dell’eccezione
System.exit(1);
 Poi il programma termina l’esecuzione segnalando che qualcosa non ha funzionato correttamente
Invece, System.exit(0) termina l’esecuzione segnalando che tutto è andato bene
 Si possono, ovviamente, adottare strategie di gestione più elaborate
 Ad esempio consentire l’immissione di un nuovo nome 119
di file se il file non viene trovato
118
Propagare eccezioni di IO: throws
 Come per le eccezioni non controllate, un metodo può non gestire un’eccezione controllata e propagare l’eccezione
 Il metodo termina la propria esecuzione
 E lascia la gestione al metodo chiamante
 Attenzione: Per dichiarare che un metodo propaga un’eccezione controllata, si contrassegna il metodo con il marcatore throws //questo metodo read non gestisce le FileNotFoundException e lo
//deve dichiarare tramite il marcatore throws
public void read(String filename) throws FileNotFoundException
{
FileReader reader = new FileReader(filename);
Scanner in = new Scanner(reader);
...
}
120
Le eccezioni di Scanner
Propagare eccezioni di IO da main
 Scanner è una classe di utilità. Non appartiene al pacchetto IO e non lancia eccezioni di IO!
 Le principali eccezioni di Scanner
 NoSuchElementException lanciata ad es. da next e nextLine se l'input non è disponibile
 InputMismatchException lanciata da nextInt nextDouble, ecc., se l'input non corrisponde al formato richiesto
 IllegalStateException lanciata da molti metodi se invocati quando lo Scanner e` “chiuso” (ovvero dopo un'invocazione del metodo close)
 Suggerimento: quando possibile usiamo solo i metodi next e nextLine di Scanner, se necessario convertiamo stringhe in numeri usando Double.parseDouble e Integer.parseInt  Se non si vuole gestire un’eccezione controllata, si può dichiarare che il metodo main la propaga
 Questo è in assoluto il modo più semplice (anche se non il migliore) di scrivere un main in cui si possono generare eccezioni di IO
import java.io.FileNotFoundException;
// QUESTO CODICE
import java.io.FileReader;
// COMPILA!
import java.util.Scanner;
public class MyInputReader
{
public static void main(String[] args) throws IOException
{ String filename = “filename.txt”;
FileReader reader = new FileReader(filename);
Scanner in = new Scanner(reader);
}
}
121
122
Gestire le eccezioni di input
 Abbiamo detto che un flusso va sempre chiuso (usando il metodo close) per rilasciare le risorse
 E se l’esecuzione viene interrotta da una eccezione prima dell’invocazione del metodo close?
 Si verifica una situazione di potenziale instabilità
 Si può usare la clausola finally
 Il corpo di una clausola finally viene eseguito comunque, indipendentemente dal fatto che un’eccezione sia stata generata oppure no
Materiale di complemento
(capitolo 11 – eccezioni di IO)
123
124
Gestire eccezioni di input
Progettare nuovi tipi di eccezioni
 Esempio: uso di try/finally
 Ora sappiamo
 programmare nei nostri metodi il lancio di eccezioni appartenenti alla libreria standard (throw new …)
 gestire eccezioni (try/catch)
 lasciare al chiamante la gestione di eccezioni sollevate nei nostri metodi (throws nell’intestazione del metodo)
 Non sappiamo ancora creare eccezioni solo nostre
 Ad esempio, se vogliamo lanciare l’eccezione LaNostraEccezioneException come facciamo?
 Per ora saltiamo questo argomento (sezione 11.6)
FileReader reader = new FileReader(filename);
Scanner in = new Scanner(reader);
readData(in);
reader.close(); // può darsi che non si arrivi mai qui
FileReader reader = new FileReader(filename);
try
{
Scanner in = new Scanner(reader);
readData(in);
}Se finally
{ reader.close(); } // questa istruzione viene eseguita
// comunque prima di passare
// l’eccezione al suo gestore
• Lo riprenderemo quando avremo studiato il concetto di ereditarietà in Java
125
126
127