Quando si estende una classe, si possono aggiungere componenti e metodi.
Possono avere gli stessi nomi di componenti e metodi già esistenti
class Studente {
String nome;
int anno;
}
Esempio: nella classe Borsista metto il numero di anni da cui va avanti la borsa di studio.
class Borsista extends Studente {
int stipendio;
double anno;
}
Si può fare
Gli oggetti Borsista hanno due componenti con lo stesso nome
Una componente sta nella "parte Studente", l'altra sta nella parte nuova
public static void main(String args[]) {
Borsista b=new Borsista();
b.anno=12;
}
Il valore 12 viene messo nella componente nuova
In una variabile Studente mi aspetto che ci sia un oggetto che abbia almeno tutte le caratteristiche di uno Studente
Questo deve valere anche se in s c'è un riferimento a un Borsista
In una variabile di una classe, ci può essere:
public static void main(String args[]) {
Studente s=new Borsista();
s.anno=12;
}
In questi esempi, si capisce quale oggetto c'è in una variabile.
In generale, non si può sapere per certo:
public static void main(String args[]) {
Borsista b=new Borsista();
Studente s=new Studente();
if(metodo())
s=b;
s.anno=12.0;
}
Non c'è modo di stabilire a priori se in s ci sarà uno Studente oppure un Borsista
So soltanto che c'è un oggetto Studente oppure un oggetto di una sottoclasse
Concettualmente, ogni Borsista è uno Studente
In Java, sono comunque due classi diverse, e quindi due tipi diversi
Le regole sulle variabili, ecc. servono a "simulare" l'idea che ogni Borsista è uno Studente in un linguaggio in cui sono classi diverse
Regola: la scelta della componente dipende dalla variabile
public static void main(String args[]) {
Borsista b=new Borsista();
Studente s=b; // stesso oggetto
s.anno=10; // componente vecchia
b.anno=10.2; // componente nuova;
}
Come accedere alla componente intera di Borsista: mettere l'oggetto in una variabile Studente
public static void main(String args[]) {
Borsista b=new Borsista();
Studente s=b;
s.stipendio=12; // errore
}
L'oggetto che sta in s ha una componente stipendio
Gli oggetti Studente no
Il controllo è statico: le variabili Studente possono non avere stipendio
I metodi si possono ridefinire
Esempio di classe:
class Studente {
String nome;
int anno;
void stampa() {
System.out.println(nome+" "+anno);
}
}
Tutte le sottoclassi hanno un metodo stampa ereditato.
Borsista eredita il metodo stampa di Studente
Però può anche ridefinirlo:
class Borsista extends Studente {
int stipendio;
double anno;
void stampa() {
System.out.println(nome+" "+super.anno+" "+stipendio+" "+this.anno);
}
}
Non è un errore!
In fondo, è quello che uno si aspetta: nella classe nuova ci sarà una nuova versione del metodo di stampa
super.anno è la componente anno
della sovraclasse, this.anno quella nuova
(si può omettere il this)
In questo caso, super.nome, this.nome e nome sono la stessa cosa
Regola generale: se ho una variabile di una classe, posso solo accedere a metodi e componenti definiti nella classe
L'unico caso particolare è:
Cosa succede:
Imparare a memoria
La classe Object ha questi metodi (ne ha altri):
Tutte le classi hanno (ereditano) tutti i metodi di Object
Di solito, questi metodi vengono ridefiniti.
Esempio: per la classe Point:
Perchè System.out.println stampa un oggetto qualsiasi?
System.out è un oggetto della classe PrintStream che ha un metodo println
class PrintStream {
...
void println(Object o) {
...
}
}
Cosa fa: stampa o.toString()
import java.awt.*;
class Punto {
public static void main(String args[]) {
Point p=new Point(2, 3);
System.out.println(p);
}
}
Cosa succede?
Dato che la println ha un argomento Object, si può passare uno Studente:
public static void main(String args[]) {
Studente s=new Studente();
System.out.println(s);
}
Cosa succede:
Non è quello che si vorrebbe venisse stampato (i dati dello studente)
Basta inserire un metodo con questo nome nella classe
Cosa ci si aspetta: che ritorni una stringa che rappresenta l'oggetto
class Studente {
String nome;
int anno;
public String toString() {
return "["+nome+","+anno+"]";
}
}
Sul public ci ritorneremo
Se Studente ha un metodo stampa() si può pensare di fare:
public static void main(String args[]) {
Object o=new Studente();
o.stampa();
}
La classe Object non ha il metodo stampa()
Il discorso "il metodo è quello della sottoclasse" vale solo se il metodo sta sia nella sottoclasse che nella sovraclasse
Le classi "serie" hanno sia toString che equals ridefiniti
Vediamo un passo per volta
Intanto, l'intestazione:
class Studente {
String nome;
int anno;
...
public boolean equals(Object o) {
...
}
}
Nella classe Object ha come parametro un Object
Quando si eredita, il tipo degli argomenti non può cambiare
Se voglio ridefinire il metodo, devo usare gli stessi parametri che ci sono nella sovraclasse
Se definisco un metodo equals(Studente s), la classe contiene sia questo che equals(Object o), che è stato ereditato da Object
public boolean equals(Object o) {
return (this.nome==o.nome)&&(this.anno==o.anno);
}
La variabile o è un Object
Non ha le componenti nome e anno
Prima va fatto il cast:
public boolean equals(Object o) {
Studente s;
s=(Studente) o;
return (this.nome==s.nome)&&(this.anno==s.anno);
}
Dato che la componente nome è una stringa, va confrontata usando equals
public boolean equals(Object o) {
Studente s;
s=(Studente) o;
return (this.nome.equals(s.nome))&&
(this.anno==s.anno);
}
this non può valere null (è l'oggetto di invocazione)
o può valere null
if(s.equals(null))
...
Va aggiunto il controllo:
public boolean equals(Object o) {
Studente s;
if(o==null)
return false;
s=(Studente) o;
return (this.nome.equals(s.nome))&&
(this.anno==s.anno);
}
Dato che this non può valere null, se o vale null i due oggetti non sono uguali
Anche le componenti nome possono valere null
Se this.nome vale null, allora l'invocazione this.nome.equals(...) genera un errore
Non dovrebbe: se s.nome vale null allora i due oggetti potrebbero essere uguali, altrimenti non lo sono (ma non si tratta di una condizione di errore)
public boolean equals(Object o) {
Studente s;
if(o==null)
return false;
s=(Studente) o;
if(this.nome==null) {
if(s.nome!=null)
return false;
}
else
if(!this.nome.equals(s.nome))
return false;
if(this.anno!=s.anno)
return false;
return true;
}
L'idea: per verificare se un gruppo di condizioni sono tutte vere, le verifico una per volta.
Se una condizione è falsa mi fermo e ritorno false
Altrimenti, vado avanti: se arrivo alla fine, tutte le condizioni sono vere e quindi ritorno true
Per le componenti scalari: se sono diverse ritorno false
Per gli oggetti: se this.nome vale null, allora se s.nome è diverso da null ritorno false
Se this.nome è diverso da null, uso equals come al solito
Dato che equals ha un Object come argomento, si può anche invocare con un parametro attuale che non è uno Studente
Se o non contiene uno Studente (o un oggetto di una sottoclasse) allora il cast dà un errore a runtime
Se due oggetti sono di classi diverse, allora non sono uguali
Confronto fra classi:
if(this.getClass()!=o.getClass())
return false;
class Studente {
String nome;
int anno;
public String toString() {
return "["+nome+","+anno+"]";
}
public boolean equals(Object o) {
Studente s;
if(o==null)
return false;
if(this.getClass()!=o.getClass())
return false;
s=(Studente) o;
if(this.nome==null) {
if(s.nome!=null)
return false;
}
else
if(!this.nome.equals(s.nome))
return false;
if(this.anno!=s.anno)
return false;
return true;
}
}