Il metodo clone di Object – Lezione 20 di Java Avanzato

Clonare una classe con la copia superficiale del metodo clone

In Java quando si vuole duplicare una classe si utilizza il metodo clone messo a disposizione dalla classe Object. L’utilizzo di questo metodo consente la copia di un oggetto esistente, la cui vita risulta indipendente dall’originale. Eventuali modifiche sull’oggetto clonato non avranno ripercussioni sull’oggetto clonato.

In Java nella classe Object è presente il metodo clone:

  protected Object clone() throws CloneNotSupportedException

Tale metodo effettua una cosiddetta “copia superficiale” dell’oggetto su cui viene chiamato. La JVM crea un nuovo spazio di memoria e copia in esso campo per campo tutto il contenuto dell’oggetto originale. Queste assegnazioni avvengono utilizzando l’operatore di assegnazione =, da qui il nome di “copia superficiale” (semplice).

Il contratto di clone:

  1. Precondizioni: this estende Clonable;
  2. Postcondizioni: restituisce una copia “copia superficiale” di this;
  3. Trattamento degli errori: lancia CloneNotSupportedException.

Siccome il metodo clone si trova in Object, verrà ereditato da tutte le classi. Questo non significa che tutte le classi in Java supportano la clonazione, anzi… quando si vuole implementare la clonazione si deve estendere l’interfaccia Clonable. Si tratta di un’interfaccia vuota che non contiene né metodi né campi e serve solo a “taggare” le classi, in questo caso l’interfaccia Clonable indica che la classe implementa la clonazione. Questo tipo classi vengono chiamate tag interface, non hanno nessun significato per il compilatore (non obbligano ad implementare nulla), ma servono solo per motivi di documentazione: nel nostro caso dato che il metodo clone viene ereditato da tutte le classi non è possibile scrivere nel contatto di clone che chi eredita questo metodo supporta la clonazione. Utilizzeremo dei tag per indicare quali classi implementano determinate funzioni.

Differenza tra copia superficiale e copia profonda di clone

In alcuni casi una copia superficiale di un oggetto non è sufficiente. Quando si effettua una copia superficiale di un oggetto viene copiato il suo indirizzo di memoria, avremo due puntatori che fanno riferimento allo stesso oggetto. Una futura modifica all’oggetto puntato influenzerà entrambi i puntatori. A tale scopo è stata definita la copia profonda che consente di clonare ricorsivamente tutti gli oggetti puntati.

La scelta di utilizzo tra copia superficiale e profonda dipende non solo dai singoli campi, se sono mutabili o meno, ma anche dal contesto applicativo. La prima è rapida ed economica ma più rischiosa perché duplica i riferimenti, la seconda è lenta e costosa ma più sicura perché duplica gli oggetti.

Facciamo un esempio in cui gli Employee all’interna di un’azienda sono strutturati ad albero, ogni Employee avrà un capo ufficio fino ad arrivare al presidente dell’azienda che non avrà nessun capoufficio. Un singolo Employee sarà:

  public class Exmployee{
    private int salary;
    private Date hireDate;
    private String name;
    private Employee boss;
  }

Che tipo di copia bisogna fare per ogni campo?

  1. salary, essendo un semplice intero effettuiamo una copia superficiale;
  2. hireDate, dato che un’eventuale modifica all’oggetto originale influenzerebbe anche l’oggetto clonato effettueremo una copia profonda;
  3. name, useremo una copia superficiale perché è di tipo String, anche se venisse modificato il nome, l’oggetto clonato non avrebbe ripercussioni;
  4. boss, dipende dal contesto. In generale scegliamo una copia superficiale perché se non c’è un’ottima ragione per la copia profonda non copieremo ricorsivamente tutti gli oggetti fino alla radice.

Se ci troviamo in una classe esterna al pacchetto e vogliamo clonare un oggetto non potremo chiamare il metodo clone di Object dato che lo si potrebbe chiamare solo su se stesso o sui sottotipi.

Per rendere effettivamente clonabile un oggetto bisogna:

  1. Implementare l’interfaccia Clonable, in caso contrario verrebbe violato il contratto e la copia superficiale non compilerebbe;
  2. Effettuare l’overriding del metodo clone e renderlo pubblico. Siccome il metodo clone in Object è definito protected fa si che una classe può invocare clone solo per clonare i propri oggetti;
  3. Decidere in base al contesto se lasciare o meno la clausola throws dell’eccezione verificata CloneNotSuopportedException. Una sua eventuale eliminazione impedisce a tutte le possibili sottoclassi di poter ridefinire il metodo clone in modo da poter lanciare quest’eccezione;
  4. Implementiamo il metodo clone vero e propria, utilizzando la copia superficiale o profonda quando opportuno;

Riprendendo l’esempio precedente avremo:

  public class Employee implements Cloneable {
    public Employee clone() throws CloneNotSupportedException {
      Employee e = (Employee) super.clone();
      e.hireDat e = (Date) hireDate.clone();
      return e;
    }
  }

Effettuiamo la clonazione con il metodo clone di Object, che effettua una copia preliminare superficiale. Dato che restituisce un tipo Object è necessario un cast, a partire da Java 1.5 non è più necessario. Infine, in base all’analisi effettuata effettueremo una copia profonda per quei campi che la richiedono.

Indice Lezione PrecedenteLezione Successiva

Pubblicato in Guide, Java, Programmazione Taggato con: , , , , , , ,

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

*