Il metodo clone serve per copiare gli oggetti
Si può invocare su quasi tutti gli oggetti delle classi predefinite
public static void main(String args[]) { Point p, q; p=new Point(2,3); q=(Point) p.clone(); }
Perchè il cast? Poi vediamo
È un metodo di Object
È definito protected
Quindi: si può usare solo:
Il clone di Object non si può usare nelle nostre classi:
class CloneObject { public static void main(String args[]) { Object o=new Object(); Object s; s=o.clone(); } }
Viene dato questo errore in compilazione:
... clone() has protected access ...
Nella classe Object, è definito in questo modo:
protected Object clone()
Quindi:
Non tutti gli oggetti sono clonabili:
String a, b; a=new String("abcd"); b=(String) a.clone(); // errore
Per gli oggetti clonabili: il metodo ritorna un Object, quindi va fatto il cast:
Point p, q; p=new Point(2,3); q=(Point) p.clone();
Prendiamo la classe Studente e definiamo clone
class Studente { String nome; int anno; }
Facciamo overloading
class Studente { String nome; int anno; // errore logico Studente clone() { ... } }
Non va bene: clone deve fare overloading del metodo di Object
Come per equals, il metodo deve avere esattamente la stessa intestazione del metodo di Object:
class Studente { String nome; int anno; Object clone() { ... } }
Non funziona nemmeno cosí:
clone() in Studente cannot override clone() in java.lang.Object; attempting to assign weaker access privileges; was protected
A parole: si possono allargare i diritti, ma non restringere
Va dichiarato protected oppure public
Lo dichiariamo public (si poteva anche dichiarare protected, se serviva):
class Studente { String nome; int anno; public Object clone() { ... } }
Ora funziona: scriviamo il corpo
Il codice completo è:
class Studente { String nome; int anno; public Object clone() { // non del tutto corretto ... Studente s=new Studente(); s.nome=this.nome; s.anno=this.anno; return s; } }
Va bene per clonare oggetti Studente
Non va tanto bene se Studente ha sottoclassi
Supponiamo che Studente abbia 10 sottoclassi
(Non è una cosa insolita che una classe abbia 10
sottoclassi)
Scriviamo i metodi equals e clone come detto prima in ognuna di queste 11 classi
Ora, Studente viene modificato con l'aggiunta di una campo cod_fiscale
Vanno modificate tutte e 11 le classi
Non solo: altri programmatori potrebbero aver realizzato
altre sottoclassi senza che io me ne accorga;
in queste sottoclassi, equals e clone non
funzionano
Sia per equals che per clone
In una classe derivata, è opportuno definire equals e clone in termini della loro definizione nella sovraclasse:
class Borsista extends Studente { int stipendio; public String toString() { return "["+this.nome+","+this.anno+","+this.stipendio+"]"; } public boolean equals(Object o) { Borsista b; if(!super.equals(o)) return false; b=(Borsista) o; if(this.stipendio!=b.stipendio) return false; return true; } }
super.equals fa la parte dei controlli relativi alle componenti ereditate
In questo modo, se si modifica Studente, non vanno modificate le sottoclassi
Faccio la stessa cosa:
class Borsista extends Studente { int stipendio; ... public Object clone() { Borsista b=(Borsista) super.clone(); b.stipendio=this.stipendio; return b; } }
Qui abbiamo un problema
public static void main(String args[]) { Borsista s=new Borsista(); Borsista q; q=(Borsista) s.clone(); }
In esecuzione viene dato questo errore:
Exception in thread "main" java.lang.ClassCastException at Borsista.clone(Borsista.java:23) at ProvaBorsista.main(ProvaBorsista.java:6)
Facendo super.clone() nella classe Borsista, viene invocato il metodo clone di Studente
In Studente, il metodo clone è definito come un metodo che crea un oggetto Studente
Il cast a Borsita fallisce
Il metodo standard di clonazione è questo: ogni oggetto invoca il clone della sovraclasse
Il clone di Object fa la creazione del nuovo oggetto e ci copia le componenti del vecchio
Il clone di Object crea un oggetto della stessa classe di quello di partenza, non (necessariamente) un oggetto Object
La versione complessiva è complicata
Il concetto di base (che non funziona) è che ogni clone invoca il clone della sovraclasse
class Studente { ... // ancora non funziona... public Object clone() { return super.clone(); } }
Lo stesso vale per classi derivate:
class Borsista extends Studente { ... // ancora non funziona... public Object clone() { return super.clone(); } }
I metodi clone scritti sopra non funzionano:
Studente.java:35: unreported exception java.lang.CloneNotSupportedException; must be caught or declared to be thrown return super.clone();
Questo è dovuto al modo in cui è realizzato il metodo clone di Object
Il metodo clone di Object è definito in questo modo:
Questo ha due conseguenze:
Nota:
Notare che il primo errore è sintattico: se un metodo dichiara (con throw) di poter lanciare una eccezione, questa va rilanciata o catturata
Che poi il metodo lanci effettivamente l'eccezione o no, in fase di compilazione non è verificabile
Perchè tutte queste complicazioni?
Soluzioni alternative:
Problemi delle due soluzioni:
class Studente implements Cloneable { ... public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { return null; } } }
Se Borsista estende Studente:
class Borsista extends Studente { ... public Object clone() { return super.clone(); } }
Commenti:
Quando si invoca clone di Borsista:
È sempre clone di Object che fa la copia!
Se un oggetto contiene altri riferimenti a oggetti:
Classe Segmento
import java.awt.*; class Segmento { Point i, f; }
Confronto/clonazione superficiale:
Gli oggetti Point sono esattamente gli stessi
Confronto/clonazione profonda:
Gli oggetti Point non sono gli stessi, ma hanno gli stessi valori dentro
Si confrontano i riferimenti degli oggetti
Due oggetti Segmento sono equals se i loro compi sono esattamente uguali (contengono riferimenti agli stessi identici oggetti
import java.awt.*; class Segmento { Point i, f; // equals superficiale public boolean equals(Object o) { if(o==null) return false; if(this.getClass()!=o.getClass()) return false; Segmento s=(Segmento) o; return((this.i==s.i)&&(this.f==s.f)); } }
Gli oggetti vengono confrontati in base ai loro valori, non ai loro riferimenti:
import java.awt.*; class Segmento { Point i, f; public boolean equals(Object o) { if(o==null) return false; if(this.getClass()!=o.getClass()) return false; Segmento s=(Segmento) o; if(this.i==null) { if(s.i!=null) return false; } else if(!this.i.equals(s.i)) return false; if(this.f==null) { if(s.f!=null) return false; } else if(!this.f.equals(s.f)) return false; return true; } }
clone di Object fa la copia del solo oggetto
import java.awt.*; class Segmento implements Cloneable { Point i, f; public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { return null; } } }
Prima della copia:
Dopo la copia superficiale:
Ha senso richiedere una copia anche dei due oggetti Point
Questo non viene fatto da clone di Object
Va fatto manualmente:
Occorre invocare clone su ognuno degli oggetti di cui voglio fare la copia
Notare che super.clone ha tipo di ritorno Object
Per poter accedere alle componenti, devo fare il cast
Lo stesso vale per le componenti
import java.awt.*; class Segmento { Point i, f; ... public Object clone() { try { Segmento s; s=(Segmento) super.clone(); s.i=(Point) this.i.clone(); s.f=(Point) this.f.clone(); return s; } catch(CloneNotSupportedException e) { return null; } } }