1
Il tipo di dati char (capitolo 4)
SETTIMANA 3
2
Caratteri in una stringa
Sappiamo già come estrarre sottostringhe da una stringa, con il metodo substring
A volte è necessario estrarre ed elaborare sottostringhe di dimensioni minime, cioè di lunghezza unitaria
una stringa di lunghezza unitaria contiene un solo carattere, che può essere memorizzato in una variabile di tipo char anziché in una stringa
il tipo char in Java è un tipo di dato
fondamentale come i tipi di dati numerici ed il tipo boolean, cioè non è una classe
Caratteri in una stringa
La presenza del tipo di dati char non è strettamente necessaria in Java (ed è anche per questo motivo che non l’avevamo ancora studiato)
infatti, ogni elaborazione che può essere fatta su
variabili di tipo char potrebbe essere fatta su stringhe di lunghezza unitaria
L’uso del tipo char per memorizzare stringhe di lunghezza unitaria è però importante, perché
una variabile di tipo char occupa meno spazio in memoria di una stringa di lunghezza unitaria
le elaborazioni su variabili di tipo char sono più veloci
Caratteri in una stringa
Il metodo charAt della classe String restituisce il singolo carattere che si trova nella posizione indicata dal parametro ricevuto
la convenzione sulla numerazione delle posizioni in una stringa è la stessa usata dal metodo substring
Una struttura di controllo che si usa spesso è l’elaborazione di tutti i caratteri di una stringa
String s = "John";
char ch = s.charAt(2); // ch contiene 'h'
String s = "John";
for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i);
// elabora ch }
5
Elaborazioni su variabili char
Come si può elaborare una variabile di tipo char?
Una variabile di tipo char può anche essere confrontata con una costante di tipo carattere
una costante di tipo carattere è un singolo carattere racchiuso tra singoli apici (“apostrofo”)
Il carattere può anche essere una “sequenza di escape”
Una variabile carattere può essere stampata passandola a System.out.print o System.out.println
char ch = 'x';
char ch = '\u00E9'; // carattere 'è'
char nl = '\n'; // carattere di "andata a capo"
char ch = 'x';
System.out.println(ch); // stampa 'x' e va a capo
6
Caratteri e l'operatore +
Un char può essere concatenato ad una stringa con
l’operatore di concatenazione + (viene convertito in stringa, con le stesse regole della conversione dei tipi numerici)
Se invece i due operandi di + sono entrambi caratteri
Vengono automaticamente convertiti (“promossi”) in int
L'operatore + indica una normale somma tra interi
char a = 'a';
char b = 'b';
int intc = a + b;
char c = (char)(a + b);
System.out.println(“intc: ” + intc);
System.out.println(“c: “ + c);
intc: 195 c: Ã
Caratteri speciali e sequenze di “escape”
Sequenze di “escape”
Proviamo a stampare una stringa che contiene delle virgolette
Il compilatore identifica le seconde virgolette come la fine della prima stringa "Hello, ", ma poi non capisce il significato della parola World
Basta inserire una barra rovesciata \ (backslash) prima delle virgolette all’interno della stringa
// NON FUNZIONA!
System.out.println("Hello, "World"!");
Hello, "World"!
System.out.println("Hello, \"World\"!");
9
Sequenze di “escape”
Il carattere backslash all’interno di una stringa non rappresenta se stesso, ma si usa per codificare altri caratteri che sarebbe difficile inserire in una stringa, per vari motivi (sequenza di escape)
Allora, come si fa ad inserire veramente un carattere backslash in una stringa?
si usa la sequenza di escape \\
// FUNZIONA!
System.out.println("Hello, \"World\"!");
System.out.println("File C:\\autoexec.bat");
File C:\autoexec.bat
10
Sequenze di “escape”
Un’altra sequenza di escape che si usa è \n, che rappresenta il carattere di “nuova riga” o “andare a capo”
Le sequenze di escape si usano anche per inserire caratteri di lingue straniere o simboli che non si trovano sulla tastiera
System.out.println("*\n**\n***");
***
***
System.out.println("*");
System.out.println("**");
System.out.println("***");
Sequenze di “escape”
Ad esempio, per scrivere parole italiane con lettere accentate senza avere a disposizione una tastiera italiana
Queste sequenze di escape utilizzano la codifica standard Unicode http://www.unicode.org per rappresentare i caratteri di tutti gli alfabeti del mondo con 4 cifre esadecimali (codifica a 16 bit, 65536 simboli diversi)
System.out.println("Perch\u00E9?");
Perché?
Escape e caratteri di controllo
Alcune sequenze di escape possono essere usate per rappresentare dei caratteri di controllo
Ovvero caratteri Unicode che non rappresentano simboli scritti
Ma che fanno parte integrante di un flusso di testo, come tutti gli altri caratteri
SEQ.ESCAPE NOME COD.UNICODE
\n newline \u000A
\t tab \u0009
\b backspace \u0008
\r return \u000D
\f formfeed \u000C
13
Caratteri di controllo
I primi 32 caratteri nella codifica Unicode sono tutti caratteri di controllo
Il cosiddetto insieme C0 di ASCII (e Unicode)
Per chi e' interessato
• http://en.wikipedia.org/wiki/C0_and_C1_control_codes
• http://en.wikipedia.org/wiki/Control_character
Vedremo altri importanti caratteri di controllo
ETX (EndofTeXt), immesso da tastiera con <CTRL>+C
• usato per interrompere l'esecuzione di un programma
EOT (EndOfTransmission, <CTRL>+D) e SUB (SUBstitute, <CTRL>+Z)
• Usati per segnalare la fine dell'input, ad esempio di un file 14
Formattazione di numeri:
il metodo printf
Formattazione di numeri
Non sempre il formato standard per stampare numeri corrisponde ai nostri desideri
Ci piacerebbe di più visualizzare i numeri
Con due cifre decimali
Incolonnati
double total = 3.50;
final double TAX_RATE = 8.5; // aliquota d’imposta in percentuale
double tax = total * TAX_RATE / 100;
System.out.println(“Total: “ + total);
System.out.println(“Tax: “ + tax); Total: 3.5 Tax: 0.2975
Total: 3.50 Tax: 0.30
Formattazione di numeri
Java fornisce il metodo printf, che ha più parametri espliciti
Il primo parametro è una stringa di formato che contiene caratteri da stampare e specificatori di formato
• Ogni specificatore di formato comincia con il carattere %
I parametri successivi sono i valori da visualizzare secondo i formati specificati
Produce:
System.out.printf(“Total:%5.2f”, total) Total: 3.50
Spazio
17
Formattazione di numeri
Tipi di formato e modificatori di formato:
18
Caratteri di fine riga
Diversi sistemi operativi “capiscono” diversi caratteri di fine riga
Sistemi Unix usano il carattere newline (o linefeed): \n
DOS usa la sequenza carriagereturnnewline: \r\n
Per essere sicuri che la fine della riga sia riconosciuta da qualsiasi sistema operativo possiamo usare il metodo printf con il formato %n
produce Total: 3.50
System.out.printf(“Total:%5.2f%n”, total) Nuova riga
Ricevere dati in ingresso
I dati in ingresso ai programmi
I programmi visti finora non sono molto utili, visto che eseguono sempre la stessa elaborazione ad ogni esecuzione
Il programma Coins1 rappresenta sempre il medesimo borsellino…
se si vuole che calcoli il valore contenuto in un diverso borsellino, è necessario modificare il codice sorgente (in particolare, le inizializzazioni delle variabili) e compilarlo di nuovo
I programmi utili hanno bisogno di ricevere dati in ingresso dall’utente
21
L’input standard dei programmi
Il modo più semplice e immediato per fornire dati in ingresso ad un programma consiste
nell’utilizzo della tastiera
altri metodi fanno uso del mouse, del microfono…
Abbiamo visto che tutti i programmi Java hanno accesso al proprio output standard, tramite l’oggetto System.out di tipo PrintStream
Analogamente, l’interprete Java mette a disposizione dei programmi in esecuzione il proprio input standard (flusso di input), tramite l’oggetto System.in di tipo InputStream
22
La classe Scanner
Sfortunatamente, la classe InputStream non possiede metodi comodi per la ricezione di dati numerici e stringhe
PrintStream ha invece il comodissimo metodo print
Per ovviare a questo inconveniente, Java 5.0 ha introdotto la classe Scanner
Un oggetto di tipo Scanner consente di leggere da qualsiasi flusso di ingresso (ad es. un file)
Noi cominciamo ad usarlo per leggere dati in ingresso da tastiera ricevuti tramite l'oggetto System.in
Leggere l’input con la classe Scanner
Scanner in = new Scanner(System.in);
Usare la classe Scanner
Per leggere dallo standar input bisogna creare un oggetto di tipo Scanner, usando la sintassi consueta
Il parametro esplicito del costruttore di Scanner è System.in
L'oggetto in di tipo Scanner è “agganciato” allo standard input
Dato che la classe Scanner non fa parte del pacchetto
java.lang, ma del pacchetto java.util, è necessario importare esplicitamente la classe all'interno del file java che ne fa uso
Quando non si usa piu’ l’oggetto di classe Scanner e’ bene chiuderlo:
import java.util.Scanner;
in.close();
Sono due oggetti diversi!!
25
I metodi nextInt e nextDouble
Come si fa ad acquisire valori numerici da standard input?
Numero intero: metodo int nextInt()
Numero in virgola mobile: metodo double nextDouble()
Durante l’esecuzione del metodo (nextInt o nextDouble) il programma si ferma ed attende l’introduzione dell’input da tastiera, che termina quando l’utente batte il tasto Invio
nextInt restituisce un valore numerico di tipo int
NextDouble restituisce un valore numerico di tipo double
• cosa succede se l’utente non digita un numero intero (o un numero double) sulla tastiera ?? Provare!!
int number = in.nextInt();
double number = in.nextDouble();
26
Esempio
import java.util.Scanner;
public class Coins4
{ public static void main(String[] args) { Scanner in = new Scanner(System.in);
System.out.println("Quante lire?");
int lit = in.nextInt();
System.out.println("Quanti euro?");
double euro = in.nextDouble();
System.out.print("Valore totale in euro ");
System.out.println(euro + lit / 1936.27);
} }
I metodi next e nextLine
Come si fa ad acquisire stringhe da standard input?
Parola
ovvero una stringa delimitata dai caratteri di spaziatura space (SP), tab (\t), newline (\n), carriagereturn (\r)
metodo String next()
Riga
(ovvero una stringa delimitata dai caratteri \n o \r):
metodo String nextLine():
String city = in.nextLine();
String state = in.next();
Esempio
import java.util.Scanner;
public class MakePassword2
{ public static void main(String[] args) { Scanner in = new Scanner(System.in);
System.out.println("Inserire il nome");
String firstName = in.nextLine();
System.out.println("Inserire il cognome");
String lastName = in.nextLine();
System.out.println("Inserire l’eta’");
int age = in.nextInt();
String initials = firstName.substring(0, 1) + lastName.substring(0, 1);
String pw = initials.toLowerCase() + age;
System.out.println("La password e’ " + pw);
} }
29
Scanner e localizzazione
La classe Scanner prevede la possibilità di riconoscere numeri formattati secondo diverse “usanze locali”
Nel corso noi useremo la convenzione anglosassone che prevede l’uso del carattere di separazione ‘.’ tra parte intera e parte frazionaria nei numeri in virgola mobile
Per definire un oggetto Scanner che rispetta questa convenzione dovremo importare anche la classe Locale e scrivere
import java.util.Scanner;
import java.util.Locale;
...
Scanner in = new Scanner(System.in);
in.useLocale(Locale.US);
...
30
È tutto chiaro? …
1. Perché non si possono leggere dati in ingresso direttamente tramite System.in ?
2. Se in è un oggetto di tipo Scanner che legge da System.in e il nostro programma invoca
String name = in.next();
Qual è il valore di name se l’utente digita la seguente stringa?
John Q. Public
Realizzare classi
(Capitolo 3)
33
Scatole nere
Per molti guidatori
Un’automobile è una scatola nera
Non sanno come funziona
Ma sanno come usarla
Un ingegnere meccanico sa
progettare una automobile ma non i moduli di controllo elettronico
Non ne conosce il funzionamento interno
Ma sa come usarli
Scatole nere danno luogo ad incapsulamento
I dettagli non importanti vengono nascosti
Ma va identificato il concetto che meglio rappresenta una scatola nera: questo comporta astrazione
34
Scatole nere
Approccio alla programmazione orientato agli oggetti
Il software viene incapsulato in scatole nere (oggetti)
Diversi livelli di astrazione
Un utente di computer usa un programma costruito tramite oggetti senza sapere nulla di programmazione
Un programmatore può usare un oggetto senza conoscerne i dettagli di funzionamento
Gli oggetti usati da un programmatore sono programmati da... altri programmatori
Un oggetto contiene... altri oggetti
Il progetto di una classe:
BankAccount
Progettare la classe BankAccount
Vogliamo progettare la classe BankAccount, che descriva il comportamento di un conto corrente bancario
Quali caratteristiche sono essenziali al concetto di conto corrente bancario?
Possibilità di versare denaro
Possibilità di prelevare denaro
Possibilità di conoscere il saldo attuale
Queste caratteristiche definiscono l'interfaccia pubblica della classe
37
Progettare la classe BankAccount
Le operazioni consentite dal comportamento di un oggetto si effettuano mediante invocazione di metodi
Dobbiamo definire tre metodi
Tali metodi consentono di
depositare denaro nel conto
prelevare denaro dal conto
conoscere il saldo
account.deposit(1000);
account.withdraw(500);
double balance = account.getBalance();
public void deposit(double amount);
public void withdraw(double amount);
public double getBalance();
38
Definizioni di metodi
La definizione di un metodo inizia sempre con la sua intestazione (o firma, o signature), composta da
uno specificatore di accesso
• In questo caso public, altre volte vedremo private
il tipo di dati restituito dal metodo (double, void...)
il nome del metodo (deposit, withdraw, getBalance)
un elenco di parametri, eventualmente vuoto, racchiuso tra parentesi tonde
• di ogni parametro si indica il tipo ed il nome
• più parametri sono separati da una virgola public void deposit(double amount) public double getBalance()
Definizioni di metodi
L'intestazione di un metodo è seguita dal corpo del metodo stesso, composto da
un insieme di enunciati che specificano le azioni svolte dal metodo stesso
racchiusi tra parentesi graffe
public void deposit(double amount) {
//corpo del metodo }
public double getBalance() {
//corpo del metodo
Metodi di accesso e modificatori
Metodo d'accesso: accede ad un oggetto e restituisce informazioni senza modificarlo
getBalance è metodo di accesso
length della classe String è metodo di accesso
getX, getY, getWidth, getHeight della classe Rectangle sono metodi di accesso
Metodo modificatore: altera lo stato di un oggetto
deposit e withdraw sono metodi modificatori
translate della classe Rectangle è un metodo modificatore
41
Costruttori
42
Ripasso: costruire oggetti
Abbiamo detto che per creare un nuovo oggetto di una classe si usa l’operatore new seguito dal nome della classe e da una coppia di parentesi tonde
L’operatore new crea un nuovo oggetto e ne restituisce un riferimento, assegnabile ad una variabile oggetto del tipo appropriato
new BankAccount();
BankAccount account = new BankAccount();
account BankAccount
I costruttori
Nella realizzazione della classe BankAccount bisogna includere il codice per creare un nuovo conto bancario, ad esempio con saldo iniziale a zero.
Per consentire la creazione di un nuovo oggetto di una classe, inizializzandone lo stato, dobbiamo scrivere un nuovo metodo, il costruttore della classe
public BankAccount() {
//corpo del costruttore }
I costruttori hanno sempre lo stesso nome della classe
I costruttori
Sintassi:
Lo scopo principale di un costruttore è quello di inizializzare un oggetto della classe
I costruttori, come i metodi, sono solitamente
pubblici, per consentire a chiunque di creare oggetti della classe
La sintassi utilizzata per definire i costruttori è molto simile a quella dei metodi, ma
il nome dei costruttori è sempre uguale a quello della classe
i costruttori non restituiscono alcun valore e non bisogna neppure dichiarare che restituiscono void
tipoAccesso NomeClasse(TipoParametro nomeParametro,...) { //realizzazione del costruttore
}
45
Invocazione di costruttori
I costruttori si invocano soltanto con l’operatore new
L’operatore new riserva la memoria per l’oggetto, mentre il costruttore definisce il suo stato iniziale
Il valore restituito dall’operatore new è il riferimento all’oggetto appena creato e inizializzato
quasi sempre il valore dell’operatore new viene memorizzato in una variabile oggetto
BankAccount account = new BankAccount();
// ora account ha saldo zero
new BankAccount();
46
Una classe con più costruttori
Una classe può avere più di un costruttore
Ad esempio, definiamo un costruttore per creare un nuovo conto bancario con un saldo iniziale diverso da zero
public BankAccount()
{ // corpo del costruttore // inizializza il saldo a 0 }
public BankAccount(double initialBalance) { // corpo del costruttore
// inizializza il saldo a initialBalance }
Una classe con più costruttori
Per usare il nuovo costruttore di BankAccount, bisogna fornire il parametro initialBalance
Notiamo che, se esistono più costruttori in una
classe, hanno tutti lo stesso nome, perché devono comunque avere lo stesso nome della classe
questo fenomeno (più metodi o costruttori con lo stesso nome) è detto sovraccarico del nome (overloading)
il compilatore decide quale costruttore invocare basandosi sul numero e sul tipo dei parametri forniti nell’invocazione
BankAccount account = new BankAccount(500);
Una classe con più costruttori
Il compilatore effettua la risoluzione dell’ambiguità nell’invocazione di costruttori o metodi sovraccarichi
Se non trova un costruttore che corrisponda ai parametri forniti nell’invocazione, segnala un errore semantico
// NON FUNZIONA!
BankAccount a = new BankAccount("tanti soldi");
cannot resolve symbol
symbol : constructor BankAccount (java.lang.String) location : class BankAccount
49
Sovraccarico del nome
Se si usa lo stesso nome per metodi diversi, il nome diventa sovraccarico (nel senso di carico di
significati diversi…)
questo accade spesso con i costruttori, dato che se una classe ha più di un costruttore, essi devono avere lo stesso nome
accade più di rado con i metodi, ma c’è un motivo ben preciso per farlo (ed è bene farlo in questi casi)
• usare lo stesso nome per metodi diversi (che richiedono parametri di tipo diverso) sta ad indicare che viene compiuta la stessa elaborazione su tipi di dati diversi
50
Sovraccarico del nome
La libreria standard di Java contiene numerosi esempi di metodi sovraccarichi
Quando si invoca un metodo sovraccarico, il
compilatore risolve l’invocazione individuando quale sia il metodo richiesto sulla base dei parametri espliciti che vengono forniti
public class PrintStream { ...
public void println(int n) {...}
public void println(double d) {...}
...
}
Definire una classe
Definizione di classe
Sintassi:
Le variabili di esemplare memorizzano lo stato di un oggetto
La classe BankAccount deve avere un campo di esemplare che permetta di memorizzare il saldo di un oggetto di tipo BankAccount
tipoAccesso class nomeClasse
{ costruttori (intestazione e corpo) metodi (intestazione e corpo) variabili (campi) di esemplare }
53
Definire la classe BankAccount
public class BankAccount { //Costruttori
public BankAccount() { corpo del costruttore }
public BankAccount(double initialBalance) { corpo del costruttore }
//Metodi
public void deposit(double amount) { realizzazione del metodo } public void withdraw(double amount) { realizzazione del metodo } public double getBalance() { realizzazione del metodo }
//Campi di esemplare ...
} 54
È tutto chiaro? …
1. Come si può svuotare il conto bancario account usando i metodi dell’interfaccia pubblica della classe?
2. Supponete di voler realizzare una più potente astrazione di conto bancario che tenga traccia anche di un numero di conto. Come va
modificata l’interfaccia pubblica?
Variabili di esemplare
Lo stato di un oggetto
Gli oggetti (quasi tutti…) hanno bisogno di
memorizzare il proprio stato attuale, cioè l’insieme dei valori che
descrivono l’oggetto e
influenzano (anche se non necessariamente) il risultato dell’invocazione dei metodi dell’oggetto
Gli oggetti della classe BankAccount hanno bisogno di memorizzare il valore del saldo del conto bancario che rappresentano
Lo stato di un oggetto viene memorizzato mediante variabili di esemplare (o “variabili di istanza”, instance variables)
57
Dichiarare variabili di esemplare
Sintassi:
Scopo: definire una variabile nomeVariabile di tipo TipoVariabile, una cui copia sia presente in ogni oggetto della classe NomeClasse
Esempio:
public class NomeClasse { ...
tipoDiAccesso TipoVariabile nomeVariabile; ...
}
public class BankAccount { ...
private double balance;
...
} 58
Variabili di esemplare
Ciascun oggetto (“esemplare”) della classe ha una propria copia delle variabili di esemplare
tra le quali non esiste nessuna relazione: possono essere modificate indipendentemente l’una dall’altra
account1
balance 1000 BankAccount
account2
balance 2000 BankAccount
Variabili di esemplare
Così come i metodi sono di solito “pubblici” (public), le variabili di esemplare sono di solito “private”
(private)
Le variabili di esemplare private possono essere lette o modificate soltanto da metodi della classe a cui appartengono
le variabili di esemplare private sono nascoste (hidden) al programmatore che utilizza la classe, e possono essere lette o modificate soltanto mediante
l’invocazione di metodi pubblici della classe
questa caratteristica dei linguaggi di programmazione orientati agli oggetti si chiama incapsulamento o information hiding
Incapsulamento
Poiché la variabile balance di BankAccount è private, non vi si può accedere da metodi che non siano della classe (errore semantico segnalato dal compilatore)
Si possono usare solo i metodi pubblici!
/* codice interno ad un metodo che non appartiene a BankAccount */
double b = account.balance; // ERRORE
balance has private access in BankAccount
double b = account.getBalance(); // OK
61
Incapsulamento
L’incapsulamento ha molti vantaggi, soltanto pochi dei quali potranno essere evidenziati in questo corso di base
Il vantaggio fondamentale è quello di impedire l’accesso incontrollato allo stato di un oggetto, impedendo così anche che l’oggetto venga
(accidentalmente o deliberatamente) posto in uno stato inconsistente
Il progettista della classe BankAccount potrebbe definire (ragionevolmente) che soltanto un saldo non negativo rappresenti uno stato consistente per un conto bancario
62
Incapsulamento
Dato che il valore di balance può essere modificato soltanto invocando i metodi deposit o withdraw, il progettista può impedire che diventi negativo, magari segnalando una condizione d’errore
Se invece fosse possibile assegnare direttamente un valore a balance dall’esterno, ogni sforzo del
progettista di BankAccount sarebbe vano
Si noti che, per lo stesso motivo e anche per realismo, non esiste un metodo setBalance, dato che il saldo di un conto bancario non può essere impostato ad un valore qualsiasi!
È tutto chiaro? …
1. Si supponga di modificare la classe
BankAccount in modo che ogni conto bancario abbia anche un numero di conto. Come si modificano i campi di esemplare?
2. Quali sono i campi di esemplare della classe Rectangle?
Realizzare costruttori e
metodi
65
I metodi di BankAccount
La realizzazione dei costruttori e dei metodi di BankAccount è molto semplice
lo stato dell’oggetto (il saldo del conto) è memorizzato nella variabile di esemplare balance
i costruttori devono inizializzare la variabile balance
quando si deposita o si preleva una somma di denaro, il saldo del conto si incrementa o si decrementa della somma specificata
il metodo getBalance restituisce il valore del saldo corrente memorizzato nella variabile balance
Per semplicità, questa realizzazione non impedisce che un conto assuma saldo negativo
66
I costruttori di BankAccount
public class BankAccount {
public BankAccount() {
balance = 0;
}
public BankAccount(double initialBalance) {
balance = initialBalance;
} ...
}
Il costruttore predefinito
Cosa succede se non definiamo un costruttore per una classe?
il compilatore genera un costruttore predefinito
(senza alcuna segnalazione d’errore)
Il costruttore predefinito di una classe
è pubblico e non richiede parametri
inizializza tutte le variabili di esemplare
• a zero le variabili di tipo numerico
• a false le variabili di tipo boolean
• al valore speciale null le variabili oggetto, in modo che tali variabili non si riferiscano ad alcun oggetto
I metodi di BankAccount
public class BankAccount { ...
public void deposit(double amount) { balance = balance + amount;
}
public void withdraw(double amount) { balance = balance - amount;
}
public double getBalance() { return balance;
}
private double balance;
}
69
L’enunciato return
Sintassi:
Scopo: terminare l’esecuzione di un metodo, ritornando all’esecuzione sospesa del metodo invocante
se è presente una espressione, questa definisce il valore restituito dal metodo e deve essere del tipo dichiarato nella firma del metodo
Al termine di un metodo con valore restituito di tipo void, viene eseguito un return implicito
il compilatore segnala un errore se si termina senza un enunciato return un metodo con un diverso tipo di valore restituito
return espressione; return;
70
La classe BankAccount completa
public class BankAccount { public BankAccount() { balance = 0;
} public BankAccount(double initialBalance) { balance = initialBalance;
} public void deposit(double amount) { balance = balance + amount;
} public void withdraw(double amount) { balance = balance - amount;
} public double getBalance() { return balance;
} private double balance;
}
È tutto chiaro? …
1. Come è stato realizzato il metodo getWidth() della classe Rectangle?
2. Come è stato realizzato il metodo translate della
classe Rectangle? Parametri espliciti/impliciti
73
I parametri dei metodi
Cosa succede quando invochiamo il metodo?
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
A quale variabile balance si riferisce il metodo?
si riferisce alla variabile che appartiene all’oggetto account con cui viene invocato il metodo
account è il parametro implicito del metodo
public void deposit(double amount) { balance = balance + amount;
}
account.deposit(500);
74
Il riferimento null
Il riferimento null
Una variabile di un tipo numerico fondamentale contiene sempre un valore valido (eventualmente casuale, se non è stata inizializzata in alcun modo)
Una variabile oggetto può invece contenere
esplicitamente un riferimento a nessun oggetto valido assegnando alla variabile il valore null, che è una parola chiave del linguaggio
vedremo in seguito applicazioni utili di questa proprietà
in questo caso la variabile è comunque inizializzata BankAccount account = null;
Il riferimento null
Diversamente dai valori numerici, che in Java non sono oggetti, le stringhe sono oggetti
una variabile oggetto di tipo String può, quindi, contenere un riferimento null
String greeting = "Hello";
String emptyString = ""; // stringa vuota String nullString = null; // riferimento null int x1 = greeting.length(); // vale 5
int x2 = emptyString.length(); // vale 0
// nel caso seguente l’esecuzione del programma // termina con un errore
int x3 = nullString.length(); // errore
77
Usare un riferimento null
Una variabile oggetto che contiene un riferimento null non si riferisce ad alcun oggetto
non può essere usata per invocare metodi
Se viene usata per invocare metodi, l’interprete termina l’esecuzione del programma, segnalando un eccezione di tipo NullPointerException (pointer è un sinonimo di reference, “riferimento”)
78
Collaudare una classe
Usare la classe BankAccount
Senza sapere come sia stata realizzata la classe BankAccount, siamo in grado di utilizzarla in un programma
apriamo un nuovo conto bancario e depositiamo un po’ di denaro
double initialDeposit = 1000;
BankAccount account = new BankAccount();
System.out.println("Saldo: " + account.getBalance());
account.deposit(initialDeposit);
System.out.println("Saldo: " + account.getBalance());
Saldo: 0
Usare la classe BankAccount
Trasferiamo denaro da un conto ad un altro
Calcoliamo e accreditiamo gli interessi di un conto
double amount = 500;
account1.withdraw(amount);
account2.deposit(amount);
double rate = 0.05; // interessi del 5%
double amount = account.getBalance() * rate;
account.deposit(amount);
81
Classi di collaudo
Usiamo la classe BankAccount per risolvere un problema specifico
apriamo un conto bancario, saldo iniziale 10000 euro
sul conto viene accreditato un interesse annuo del 5% del valore del saldo, senza fare prelievi né depositi
qual è il saldo del conto dopo due anni?
BankAccount non contiene un metodo main
Compilando BankAccount.java si ottiene BankAccount.class
Ma non possiamo eseguire BankAccount.class
Dobbiamo scrivere una classe di collaudo (o di test) che contenga un metodo main nel quale
Costruiamo uno o più oggetti della classe da collaudare
Invochiamo i metodi della classe per questi oggetti
Visualizziamo i valori restituiti
82
Esempio: utilizzo di BankAccount
public class BankAccountTester
{ public static void main(String[] args)
{ BankAccount acct = new BankAccount(10000);
final double RATE = 5;
// calcola gli interessi dopo il primo anno double interest = acct.getBalance() * RATE / 100;
// somma gli interessi dopo il primo anno acct.deposit(interest);
System.out.println("Saldo dopo un anno: "
+ acct.getBalance() + " euro");
// calcola gli interessi dopo il secondo anno interest = acct.getBalance() * RATE / 100;
// somma gli interessi dopo il secondo anno acct.deposit(interest);
System.out.println("Saldo dopo due anni: "
+ acct.getBalance() + " euro");
}}
È tutto chiaro? …
1. Quando eseguo BankAccountTester, quanti oggetti di tipo BankAccount vengono costruiti? E quanti di tipo BankAccountTester?
Un programma con più classi
Per scrivere semplici programmi con più classi, si possono usare due strategie (equivalenti)
Scrivere ciascuna classe in un file diverso, ciascuno avente il nome della classe con estensione .java
• Tutti i file vanno tenuti nella stessa cartella
• Tutti i file vanno compilati separatamente
• Solo la classe di collaudo (contenente il metodo main) va eseguita
Scrivere tutte le classi in un unico file
• un file .java può contenere una sola classe public
• la classe contenente il metodo main deve essere public
• le altre non devono essere public (non serve scrivere private, semplicemente non si indica l’attributo public)
• il file .java deve avere il nome della classe public
Nel compito faremo così!!
85
Riassunto: progettare una classe
1. Capire cosa deve fare un oggetto della classe
Elenco in linguaggio naturale delle operazioni possibili
2. Specificare l'interfaccia pubblica
Ovvero, definire i metodi tramite le loro intestazioni 3. Documentare l'interfaccia pubblica
4. Identificare i campi di esemplare a partire dalle intestazioni dei metodi
5. Realizzare costruttori e metodi
Se avete problemi a realizzare un metodo forse dovete riesaminare i passi precedenti
6. Collaudare la classe con un programma di collaudo
86
Materiale di complemento (capitolo 3)
Categorie e cicli di vita delle variabili
Per ora saltiamo questa sezione (3.7)
• Ne parliamo più avanti (capitolo 9)
Parametri impliciti e il riferimento this
Per ora saltiamo questa sezione (3.8)
• Ne parliamo più avanti (capitolo 9)
89
Commenti di documentazione
90
Commentare l'interfaccia pubblica
I commenti ai metodi sono importantissimi per rendere il codice comprensibile a voi ed agli altri!
Java ha delimitatori speciali per commenti di documentazione
@param nomeparametro per descrivere un parametro esplicito
@return per descrivere il valore restituito dal metodo
/**
Preleva denaro dal conto
@param amount importo da prelevare
*/
public void withdraw(double amount)
{
//corpo del metodo }
/**
Ispeziona saldo attuale @return saldo attuale
*/
public double getBalance() {
//corpo del metodo }
Commentare l'interfaccia pubblica
Inserire brevi commenti anche alla classe, per illustrarne lo scopo
Usando commenti di documentazione in questo formato si può generare in maniera automatica documentazione in html
Genera un documento NomeClasse.html ben
formattato e con collegamenti ipertestuali, contenente i commenti a NomeClasse
/**
Un conto bancario ha un saldo
modificabile tramite depositi e prelievi
*/
public class BankAccount {
...
}
javadoc NomeClasse.java
93
Decisioni (capitolo 5)
94
L’enunciato if
Il programma precedente consente di prelevare tutto il denaro che si vuole
il saldo balance può diventare negativo
È una situazione assai poco realistica!
Il programma deve controllare il saldo ed agire di conseguenza, consentendo il prelievo oppure no
balance = balance - amount;
L’enunciato if
L’enunciato if si usa per realizzare una decisione ed è diviso in due parti
una verifica
un corpo
Il corpo viene eseguito se e solo se la verifica ha
successo
if (amount <= balance)
balance = balance - amount;
Tipi di enunciato in Java
Enunciato semplice
Enunciato composto
Blocco di enunciati
balance = balance - amount;
if (x >= 0) x=0;
{ zero o più enunciati di qualsiasi tipo }
97
Un nuovo problema
Proviamo ora ad emettere un messaggio d’errore in caso di prelievo non consentito
Problema: se si modifica la prima verifica, bisogna ricordarsi di modificare anche la seconda (es. viene concesso un fido sul conto, che può “andare in rosso”)
Problema: se il corpo del primo if viene eseguito, la verifica del secondo if usa il nuovo valore di balance, introducendo un errore logico
quando si preleva più della metà del saldo disponibile
if (amount <= balance)
balance = balance - amount;
if (amount > balance)
System.out.println("Conto scoperto");
98
La clausola else
Per realizzare un’alternativa, si utilizza la clausola else dell’enunciato if
Vantaggio: ora c’è una sola verifica
se la verifica ha successo, viene eseguito il primo corpo dell’enunciato if/else
altrimenti, viene eseguito il secondo corpo
if (amount <= balance)
balance = balance - amount;
else
System.out.println("Conto scoperto");
if (amount <= balance)
balance = balance - amount;
else
{ System.out.println("Conto scoperto");
balance = balance – OVERDRAFT_PENALTY;
}
La clausola else L’enunciato if
Sintassi:
Scopo: eseguire enunciato1 se e solo se la
condizione è vera; se è presente la clausola else, eseguire enunciato2 se e solo se la condizione è falsa
Spesso il corpo di un enunciato if è costituito da più enunciati da eseguire in sequenza; racchiudendo tali enunciati tra una coppia di parentesi graffe { } si crea un blocco di enunciati, che può essere usato come corpo
if (condizione) enunciato1
if (condizione) enunciato1 else
enunciato2
if (amount <= balance)
{ balance = balance - amount;
System.out.println("Prelievo accordato");
}
101
È tutto chiaro? …
1. Perché nell’esempio precedente abbiamo usato la condizione amount <= balance e non la
condizione amount < balance?
2. Qual è l’errore logico nell’enunciato seguente, e come lo si può correggere?
if (amount <= balance)
newBalance = balance – amount;
balance = newBalance;
102
Confrontare valori numerici
Confrontare valori
Le condizioni dell’enunciato if sono molto spesso dei confronti tra due valori
Gli operatori di confronto si chiamano operatori relazionali
> Maggiore
>= Maggiore o uguale
< Minore
<= Minore o uguale
== Uguale
!= Diverso if (x >= 0)
Attenzione:
negli operatori costituiti da due caratteri non vanno inseriti spazi intermedi
Operatori relazionali
Fare molta attenzione alla differenza tra l’operatore relazionale == e l’operatore di assegnazione =
a = 5; // assegna 5 ad a if (a == 5) // esegue enunciato enunciato // se a è uguale a 5
105
Confronto di numeri in virgola mobile
106
Confrontare numeri in virgola mobile
I numeri in virgola mobile hanno una precisione limitata ed i calcoli possono introdurre errori di arrotondamento e troncamento
Tali errori sono inevitabili e bisogna fare molta attenzione nella formulazione di verifiche che coinvolgono numeri in virgola mobile
double r = Math.sqrt(2);
double x = r * r;
if (x == 2)
System.out.println("OK");
else
System.out.println("Non ci credevi?");
Confrontare numeri in virgola mobile
Affinché gli errori di arrotondamento non influenzino la logica del programma, i confronti tra numeri in virgola mobile devono prevedere una tolleranza
Verifica di uguaglianza tra x ed y (di tipo double):
| x y | <
ε
con ε = 1E14 Scelta migliore se x,y sono molto grandi o molto piccoli
| x y | <
ε
max(|x|,|y|)con ε = 1E14 (questo valore di ε è ragionevole per numeri double)
Il codice per questa verifica è
final double EPSILON = 1E-14;
if ( Math.abs(x - y) <=
EPSILON*Math.max(Math.abs(x),Math.abs(y)) ) ...
Confrontare numeri in virgola mobile
Possiamo definire un metodo statico che verifichi l’uguaglianza con tolleranza
public class Numeric
{ public static boolean approxEqual(double x, double y) { final double EPSILON = 1E-14;
double xyMax = Math.max(Math.abs(x),Math.abs(y));
return Math.abs(x - y) <= EPSILON * xyMax;
}} double r = Math.sqrt(2);
if (Numeric.approxEqual(r * r, 2)) System.out.println("Tutto bene...");
else System.out.println("Questo non succede...");
109
Confronto di stringhe
110
Confronto di stringhe
Per confrontare stringhe si usa il metodo equals
Per confrontare stringhe ignorando la differenza tra maiuscole e minuscole si usa
Non usare mai l’operatore di uguaglianza per confrontare stringhe! Usare sempre equals
Se si usa l’operatore uguaglianza, il successo del confronto sembra essere deciso in maniera “casuale”
In realtà dipende da come è stata progettata la Java Virtual Machine e da come sono state costruite le due stringhe
if (s1.equals(s2))
if (s1.equalsIgnoreCase(s2))
Attenzione perché NON è un errore di sintassi
Confronto di stringhe
Confrontando con l’operatore di uguaglianza due riferimenti a stringhe si verifica se i riferimenti puntano allo stesso oggetto stringa
Il confronto s1 == s2 è vero, perché puntano allo stesso oggetto stringa
Il confronto s1 == s3 è falso, perché puntano ad oggetti diversi, anche se tali oggetti hanno lo stesso contenuto (sono “identici”)
String s1 = "Stringa";
String s2 = s1;
String s3 = "String";
s3 = s3 + "a"; // s3 contiene "Stringa"
Confronto di stringhe
Stringa
s1 s2
s3
String s1 = "Stringa";
String s2 = s1;
String s3 = "String";
s3 = s3 + "a";
String
Stringa String
113
Confronto di stringhe
Confrontando invece con il metodo equals due riferimenti, si verifica se i riferimenti puntano ad stringhe con lo stesso contenuto
Il confronto s1.equals(s3) è vero, perché puntano a due oggetti String identici
Nota: per verificare se un riferimento si riferisce a null, bisogna usare invece l’operatore di uguaglianza e non il metodo equals
String s1 = "Stringa";
String s2 = s1;
String s3 = "String";
s3 = s3 + "a"; // s3 contiene "Stringa"
if (s == null)
114
Ordinamento lessicografico
Se due stringhe sono diverse, è possibile conoscere la relazione che intercorre tra loro secondo
l’ordinamento lessicografico, simile al comune ordinamento alfabetico
Il confronto lessicografico tra stringhe si esegue con il metodo compareTo
Il metodo compareTo restituisce un valore int
negativo se s1 precede s2 nell’ordinamento
positivo se s1 segue s2 nell’ordinamento
zero se s1 e s2 sono identiche if (s1.compareTo(s2) < 0)
Confronto lessicografico
Partendo dall’inizio delle stringhe, si confrontano i caratteri in posizioni corrispondenti, finché una delle stringhe termina oppure due caratteri sono diversi
se una stringa termina, essa precede l’altra
• se terminano entrambe, sono uguali
altrimenti, l’ordinamento tra le due stringhe è uguale all’ordinamento alfabetico tra i due caratteri diversi
carta precede castano
c a r t a
c a s t a n o
lettereuguali r precede s
Confronto lessicografico
Il confronto lessicografico genera un ordinamento simile a quello di un comune dizionario
…con qualche differenza…
Tra i caratteri non ci sono solo le lettere
• i numeri precedono le lettere
• tutte le lettere maiuscole precedono tutte le lettere minuscole
• il carattere di “spazio bianco” precede tutti gli altri caratteri
L’ordinamento lessicografico è definito dallo standard Unicode, http://www.unicode.org
117
Confrontare oggetti
118
Confronto di oggetti
Come per le stringhe, l’operatore == tra due variabili oggetto verifica se i due riferimenti puntano allo stesso oggetto, e non verifica l'uguaglianza tra oggetti
Il metodo equals può essere applicato a qualsiasi oggetto, perché è definito nella classe Object, da cui derivano tutte le classi
È compito di ciascuna classe ridefinire il metodo equals, come fa la classe String
altrimenti il metodo equals di Object usa semplicemente l’operatore di uguaglianza
Il metodo equals di ciascuna classe deve effettuare il confronto delle caratteristiche (variabili di esemplare) degli oggetti di tale classe
Per ora usiamo equals solo per classi della libreria standard
non facciamo confronti tra oggetti di classi definite da noi
Ordinamento di oggetti
Il metodo compareTo visto per le stringhe può essere applicato a molti altri oggetti, (ma non tutti perché non è definito nella classe Object)
È compito di ciascuna classe definire in maniera opportuna il metodo compareTo, come fa la classe String, secondo una opportuna nozione di
ordinamento
Il metodo compareTo di una classe restituirà sempre un valore int
negativo se obj1 precede obj2 nell’ordinamento
positivo se obj1 segue obj2 nell’ordinamento
zero se obj1 e obj2 sono identici
È tutto chiaro? …
1.
Qual è il valore di s.length() se s contiene (a) un riferimento alla stringa vuota “” (b) un riferimento alla stringa “ “ contenente solo uno spazio (c) il valore null ?2.
Quali di questi confronti hanno errori di sintassi? Quali hanno poca utilità dal punto di vista logico?String a = “1”;
String b = “one”;
double x = 1;
double y = 3 * (1.0 / 3);
(a)
a == “1”;(b)
a == null;(c)
a.equals(“”);(d)
a == b;(e)
a == x;(f)
x == y;(g)
x – y == null;(h)
x.equals(y);121
Alternative multiple
122
Sequenza di confronti
Se si hanno più di due alternative, si usa una sequenza di confronti
if (richter >= 8)
System.out.println("Terremoto molto forte");
else if (richter >= 6)
System.out.println("Terremoto forte");
else if (richter >= 4)
System.out.println("Terremoto medio");
else if (richter >= 2)
System.out.println("Terremoto debole");
else if (richter >= 0)
System.out.println("Terremoto molto debole");
else
System.out.println(“Numeri negativi non validi");
Sequenza di confronti
Il codice seguente non funziona, perché stampa
“Terremoto molto debole” per qualsiasi valore di richter
Se si fanno confronti di tipo “maggiore di” si devono scrivere prima i valori più alti, e viceversa
if (richter >= 0) // NON FUNZIONA!
System.out.println("Terremoto molto debole");
else if (richter >= 2)
System.out.println("Terremoto debole");
else if (richter >= 4)
System.out.println("Terremoto medio");
else if (richter >= 6)
System.out.println("Terremoto forte");
else
System.out.println("Terremoto molto forte");
Sequenza di confronti
Se non si rendono mutuamente esclusive le alternative, usando le clausole else, non funziona
se richter vale 3, stampa sia “Terremoto debole” sia
“Terremoto molto debole”
if (richter >= 8) // NON FUNZIONA!
System.out.println("Terremoto molto forte");
if (richter >= 6)
System.out.println("Terremoto forte");
if (richter >= 4)
System.out.println("Terremoto medio");
if (richter >= 2)
System.out.println("Terremoto debole");
if (richter >= 0)
System.out.println("Terremoto molto debole");