Cose viste finora:
Ora: due precisazioni, più cose particolari
I tipi dei parametri e del valore di ritorno non vengono cambiati:
class Sopra { Sopra esempio(Sopra x) { ... } }
Se faccio una classe estesa:
class Sotto extends Sopra { }
Il metodo esempio ha sempre Sopra come parametro e valore di ritorno
È come se il metodo fosse scritto, identico, nella classe di sotto
class Sotto extends Sopra { Sopra esempio(Sopra x) { ... } }
Notare i tipi dei valori di ritorno e argomenti
(sono sempre di tipo Sopra)
Il metodo ereditato si invoca su un
oggetto della sottoclasse,
ma parametri e valori
di ritorno sono come prima.
Sopra a, b, c; Sotto z; a=b.esempio(c); a=z.esempio(c);
Come oggetto di invocazione posso usare
un Sopra o un Sotto
(il metodo è sia in Sopra
che in Sotto)
I parametri e il valore di ritorno rimangono Sopra
Il costruttore è un metodo che ritorna un oggetto
Il fatto che i costruttori non si ereditano si può vedere cosí:
il costruttore della sovraclasse restiuisce sempre un oggetto della sovraclasse
Quindi, il costruttore rimane un costruttore della sovraclasse, ossia non si eredita
Quando si ridefinisce un metodo, non si possono restringere gli accessi
Se un metodo è public in una classe, nella classe estesa non può essere private o con accesso di pacchetto
Se è con accesso di pacchetto, nella classe estesa non può essere private
Se è protected, allora deve rimanere tale, oppure diventare public
Capita di ridefinire classi predefinite, che hanno metodi public
import java.awt.*; class NewPoint extends Point { void move(int x, int y) { ... } }
Viene segnalato questo errore:
move(int,int) in NewPoint cannot override move(int,int) in java.awt.Point; attempting to assign weaker access privileges; was public
Il problema: il metodo move ha modificatore public in Point
In NewPoint, sto dando diritti di pacchetto
È un accesso più restrittivo, quindi non si può fare
Soluzione: dichiaro move come public nella classe estesa
import java.awt.*; class NewPoint extends Point { public void move(int x, int y) { ... } }
Quando appare questo errore, basta mettere public il metodo ridefinito
class NewPoint extends Point { ... }
Posso anche fare:
NewPoint q=new NewPoint(); Point p; p=q;
Regola generale:
in una variabile di un tipo, posso sempre mettere l'indirizzo di un oggetto di una sottoclasse
Serve a far capire quali classi sono estensioni di altre
Ogni riferimento a oggetto si può mettere in una variabile del suo tipo, oppure di un tipo ottenuto seguendo le freccie.
Non si può fare!
Point p=new Point(); NewPoint q; q=p; // errore!
Non si può mettere l'indirizzo di un oggetto di una sovraclasse in una sottoclasse
Regola mnemonica basata su Object: più avanti
class Sopra { int x; void metodo() { System.out.println("Sono Sopra"); } }
Poi la estendo, con nuove componenti e metodi:
class Sotto extends Sopra { String x; void metodo() { System.out.println("Sono Sotto"); } }
Le componenti vecchie sono ancora lí:
Ora vediamo come si accedono
Cosa succede se metto un riferimento a un oggetto in una variabile dell'altro?
class Prova { public static void main(String args[]) { Sotto b; b=new Sotto(); Sopra o; o=b; // valido } }
Posso mettere gli oggetti in variabili di sovraclassi (ma non il contrario)
Nell'esempio di sopra, ho due variabili di tipi diversi che puntano allo stesso oggetto.
Dopo aver fatto o=b, cosa succede quando faccio o.x oppure o.metodo()?
class Prova { public static void main(String args[]) { Sopra o; o=new Sotto(); // valido o.x=12; // ok o.x="abcd"; // errore! o.metodo(); // stampa "Sono Sotto" } }
Le componenti sono quelle del tipo della variabile.
Il metodo è quello del tipo dell'oggetto
Metto il riferimento a un oggetto in una variabile di una sovraclasse
Ho due tipi (tipo dell'oggetto e della variabile)
Cosa succede quando uso una componente oppure un metodo?
Oggetto in una variabile di sovraclasse: si può sempre fare
Il contrario: serve il cast
Sopra o; Sotto b; b=new Sotto(); o=b; Sotto c; c=b; // ok c=o; // errore c=(Sotto) o; // ok } }
Se voglio rimettere l'oggetto in una variabile dello stesso tipo, devo rifare il cast
Sulle variabili:
Se la variabile è di un tipo, ma l'oggetto è di un altro:
Esistono molte classi che non abbiamo visto
Esempio:
Tutte le classi Java sono sottoclassi di Object:
Se una classe non ha extends AltraClasse, è come se avesse extends Object
Per quello che si è detto finora:
Object o; o=new Point(); o=new Rectangle(); o=new FileReader("prova.txt");
Sono tutti assegnamenti validi
L'indirizzo di un oggetto si può sempre mettere in una variabile di una... sovraclasse? sottoclasse?
Regola facile per ricordare cosa si può fare:
tutti gli oggetti si possono memorizzare in una variabile Object
Dato che Object è sovraclasse di tutte le altre...
La class Object non ha componenti
Ha alcuni metodi che ci interessano (ne ha altri):
Nota: equals confronta l'indirizzo degli oggetti
Quindi, ritorna true solo se i due oggetti sono lo stesso oggetto
toString ritorna la stringa con il nome della classe e l'indirizzo dell'oggetto
Di solito, i metodi equals e toString vengono ridefiniti
toString dovrebbe convertire l'oggetto in stringa.
equals confronta gli oggetti
class Studente { String nome; int esami; double media; }
Questa classe eredita il metodo toString da Object
Però il metodo di Object restituisce solo l'indirizzo dell'oggetto, non il suo contenuto
Studente s; s=new Studente("Pippo", 12, 19.2); System.out.println(s.toString());
Stampa:
Studente@256a7c
Ridefinizione di toString: dovrebbe restituire i dati dello studente
Deve restituire una stringa che rappresenta l'oggetto
Di solito, si ottiene concatenando i dati
class Studente { String nome; int esami; double media; public String toString() { return "["+this.nome+" "+ this.esami+" "+ this.media+"]"; } }
il + fra stringhe restituisce la concatenazione
Notare public
Dato che il metodo di Object ha il modificatore public, anche questo lo deve avere
Posso stampare i dati di uno studente:
class ProvaStudente { public static void main(String args[]) { Studente s; s=new Studente(); s.nome="Pippo"; s.esami=1; s.media=19; System.out.println(s.toString()); } }
Se println viene invocato su un oggetto, invoca toString su di esso
class ProvaStudente { public static void main(String args[]) { Studente s; s=new Studente(); s.nome="Pippo"; s.esami=1; s.media=19; System.out.println(s); } }
Funziona anche cosí
Più complesso
Firma di equals in Object:
public boolean equals(Object o) { ... }
Il parametro è un Object!
Quando si eredita:
class Studente { ... public boolean equals(Object o) { ... } }
Viene restituito true solo se l'oggetto passato è lo stesso
Di solito, voglio che ritorni true se le componenti hanno gli stessi valori
Se definisco un metodo equals(Studente) sto usando il sovraccarico:
class Studente { ... public boolean equals(Studente o) { ... } }
È come se avesse tutti e due i metodi (quello nuovo e quello ereditato)
class Studente { ... // ereditato public boolean equals(Object o) { ... } // nuovo public boolean equals(Studente o) { ... } }
Non si fa in questo modo!
Devo scrivere un metodo che abbia questa firma:
public boolean equals(Object o)
Dato che o è una variabile Object, non posso usare o.nome, o.esami, ecc:
return o.esami==this.esami ... // errore
Prima faccio il cast, e poi confronto:
class Studente { ... public boolean equals(Object o) { Studente s; s=(Studente) o; return ((s.nome.equals(this.nome)) && (s.esami==this.esami) && (s.media==this.media)); } }
Questo sovrascrive il metodo equals
Alcune classi rappresentano insiemi di oggetti
Alcuni metodi di queste classi invocano il metodo equals
Il metodo che viene invocato è equals(Object), ossia il metodo che ha un Object come argomento
Non viene usato l'eventuale metodo equals(Studente)
Per ogni classe:
Va prima controllato che il parametro non sia null
public boolean equals(Object o) { if(o==null) return false; Studente s; s=(Studente) o; return ((s.nome.equals(this.nome)) && (s.esami==this.esami) && (s.media==this.media)); }
Se s.nome vale null, l'invocazione di equals dà errore
Soluzione: se s.nome vale null, allora devo solo controllare se this.nome vale null
Per gli oggetti: se il primo vale null, faccio il confronto diretto, altrimenti faccio il confronto con equals
public boolean equals(Object o) { if(o==null) return false; Studente s; s=(Studente) o; if(s.nome==null) { if(this.nome!=null) return false; } else if(!s.nome.equals(this.nome)) return false; return ((s.esami==this.esami) && (s.media==this.media)); } }
I riferimenti a oggetti si possono tutti memorizzare nelle variabili Object
Memorizzate i metodi di Studente: per la altre classi, saranno sempre fatti in questo modo
class Studente { String nome; int esami; double media; public String toString() { return "["+this.nome+" "+ this.esami+" "+ this.media+"]"; } public boolean equals(Object o) { if(o==null) return false; Studente s; s=(Studente) o; return ((s.nome.equals(this.nome)) && (s.esami==this.esami) && (s.media==this.media)); } }
Firma del metodo:
Object clone()
Va ridefinito, se lo voglio usare
Non lo facciamo (usiamo solo il clone per classi predefinite)
Se faccio:
Point q=p.clone();
Il risultato di clone è un Object, mentre Point è una sua sottoclasse
È il contrario che si può
fare senza cast
(mettere un oggetto di una sottoclasse
in una variabile della sovraclsse)
Per la cosa inversa, serve il cast:
Point q=(Point) p.clone();
È un altro metodo di Object
Ci servirà ridefinirlo
Cosí può andare:
public int hashCode() { return 0; }
Per le variabili:
Ridefinizione:
Definire un metodo che funziona su tutte le classi
Esempio: ricerca in un vettore di oggetti
static boolean presente(Object v[], Object e) { int i; for(i=0; i<v.length; i++) if(e.equals(v[i])) return true; return false; }
Funziona per tutti i vettori di oggetti
Attenzione a null:
L'elemento e potrebbe valore null
In questo caso, e.equals(...) è un errore
Dato che ogni oggetto si può mettere in un Object, posso passare un punto a un metodo che si aspetta un Object
Lo stesso vale per i vettori
public static void main(String args[]) { Point p=new Point(0,0); Point vp[]=new Point[5]; // creazione del vettore System.out.println(presente(vp, p)); Rectangle vr[]=new Rectangle[5]; Rectangle r=new Rectangle(0,0,10,10); Rectangle vr[]=new Rectangle[5]; // creazione del vettore System.out.println(presente(vr, r)); }
Quando faccio e.equals nel metodo, viene invocato quello dell'oggetto
Quindi, se il parametro è un Point, viene invocato l'equals di Point
Se il parametro è un Rectangle, viene invocato il suo equals, ecc.
D'ora in avanti, se possibile, facciamo metodi che ricevono Object
Questi possono ricevere come parametro un oggetto qualsiasi
Cosa importante: ridefinire equals
Come definire oggetti di un certo tipo:
visto
Esempio: insiemi di punti
import java.awt.*; class InsiemePoint { private Point elementi[]; InsiemePoint() { this.elementi=new Point[0]; } void stampa() { int i; for(i=0; i<this.elementi.length; i++) System.out.println(this.elementi[i]); } boolean verificaVuoto() { return this.elementi.length==0; } boolean presente(Point x) { int i; for(i=0; i<this.elementi.length; i++) if(this.elementi[i].equals(x)) return true; return false; } void inserisci(Point x) { if(this.presente(x)) return; Point nuovo[]; nuovo=new Point[this.elementi.length+1]; System.arraycopy(this.elementi, 0, nuovo, 0, this.elementi.length); nuovo[nuovo.length-1]=x; this.elementi=nuovo; } void elimina(Point x) { if(!this.presente(x)) return; Point nuovo[]; nuovo=new Point[this.elementi.length-1]; System.arraycopy(this.elementi, 0, nuovo, 0, this.elementi.length-1); int i; for(i=0; i<this.elementi.length-1; i++) if(this.elementi[i]==x) { nuovo[i]=this.elementi[this.elementi.length-1]; break; } this.elementi=nuovo; } }
Se voglio un insieme di Studente, devo riscrivere tutta la classe
Soluzione: faccio un insieme di Object:
class InsiemeObj { private Object elementi[]; InsiemeObj() { this.elementi=new Object[0]; } void stampa() { int i; for(i=0; i<this.elementi.length; i++) System.out.println(this.elementi[i]); } boolean verificaVuoto() { return this.elementi.length==0; } boolean presente(Object x) { int i; for(i=0; i<this.elementi.length; i++) if(this.elementi[i].equals(x)) return true; return false; } void inserisci(Object x) { if(this.presente(x)) return; Object nuovo[]; nuovo=new Object[this.elementi.length+1]; System.arraycopy(this.elementi, 0, nuovo, 0, this.elementi.length); nuovo[nuovo.length-1]=x; this.elementi=nuovo; } void elimina(Object x) { if(!this.presente(x)) return; Object nuovo[]; nuovo=new Object[this.elementi.length-1]; System.arraycopy(this.elementi, 0, nuovo, 0, this.elementi.length-1); int i; for(i=0; i<this.elementi.length-1; i++) if(this.elementi[i]==x) { nuovo[i]=this.elementi[this.elementi.length-1]; break; } this.elementi=nuovo; } }
Esempio di programma:
import java.awt.*; class ProvaIns { public static void main(String args[]) { InsiemeObj io; io=new InsiemeObj(); io.inserisci(new Point(12,43)); io.inserisci(new Point(3,-2)); io.inserisci(new Point(4,12)); io.inserisci(new Point(3,-2)); io.elimina(new Point(3,-2)); io.stampa(); } }
Quando faccio io.inserisci(new Point(12,43));:
In una variabile Object posso mettere un oggetto qualsiasi
Quindi si può fare
Quando faccio presente(p) cosa succede?
boolean presente(Object x) { int i; for(i=0; i<this.elementi.length; i++) if(x.equals(this.elementi[i])) return true; return false; }
x è una variabile Object
Ma contiene un riferimento a un oggetto Point
Viene invocato equals di Point
Il metodo equals di Object è fatto (più o meno) cosí:
public boolean equals(Object o) { if(o==null) return false; return this==o; }
Vale true solo se si tratta dello stesso oggetto
Di solito, due oggetti vanno considerati uguali se hanno gli stessi valori
Se non faccio la ridefinizione, viene invocato il metodo equals di Object, che non funziona
In un insieme posso mettere sia punti che rettangoli!
import java.awt.*; class Sia { public static void main(String args[]) { InsiemeObj io; io=new InsiemeObj(); io.inserisci(new Point(12,43)); io.inserisci("abcd"); io.inserisci(new Rectangle(0,0,10,10)); io.elimina(new Point(3,-2)); io.stampa(); } }
Fin qui, va bene (è un insieme misto)
import java.awt.*; class Sia { public static void main(String args[]) { InsiemeObj io; io=new InsiemeObj(); io.inserisci(new Point(12,43)); io.inserisci("abcd"); io.inserisci(new Rectangle(0,0,10,10)); io.inserisci(new Studente()); if(io.presente(new Point(12,43)) System.out.println("Ci sta"); } }
Quando si va a fare io.presente(new Point(12,43), viene invocato, a un certo punto, il confronto fra lo Studente e questo punto.
Ma equals di Studente è fatto cosí:
public boolean equals(Object o) { if(o==null) return false; Studente s; s=(Studente) o; return ((s.nome.equals(this.nome)) && (s.esami==this.esami) && (s.media==this.media)); }
Non si può fare il cast da Point a Studente (non sono sottoclassi l'uno dell'altro)
Viene prodotto un errore
Se due oggetti sono di classi diverse, equals deve dare false, e non tentare il cast
public boolean equals(Object o) { if(o==null) return false; if(this.getClass()!=o.getClass()) return false; Studente s; s=(Studente) o; if(s.nome==null) { if(this.nome!=null) return false; } else if(!s.nome.equals(this.nome)) return false; return ((s.esami==this.esami) && (s.media==this.media)); } }
Il metodo getClass ritorna il tipo dell'oggetto
Non serve sapere come funziona: il metodo equals va fatto sempre con queste tre parti prima: confronto con null confronto classi, e cast. Soltanto dopo viene il confronto vero e proprio.