W3docs

Clonazione di Oggetti in Java

Copia oggetti Java con clone(), l'interfaccia Cloneable e le differenze tra copia superficiale e copia profonda.

Clonare un oggetto significa produrre un nuovo oggetto con lo stesso stato — una copia che puoi modificare indipendentemente dall'originale. La risposta integrata di Java è Object.clone() insieme all'interfaccia marcatore Cloneable, ma il design presenta abbastanza irregolarità che la maggior parte del codice moderno preferisce un copy constructor o un factory method. Questo capitolo mostra entrambe le strade e la trappola che si nasconde tra di esse.

La via integrata: Object.clone()

Object.clone() è protected e produce una copia superficiale campo per campo dell'istanza. Per usarla devi:

  1. Far implementare alla tua classe Cloneable — un'interfaccia marcatore senza metodi. Senza di essa, clone() lancia CloneNotSupportedException.
  2. Eseguire l'override di clone() per renderlo public e (di solito) restringere il tipo di ritorno alla classe effettiva.
public class Box implements Cloneable {
  int size;

  @Override
  public Box clone() {
    try {
      return (Box) super.clone();
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);   // can't happen — we implement Cloneable
    }
  }
}

super.clone() produce la copia effettiva. Il blocco try/catch è burocrazia: l'eccezione checked è dichiarata su Object.clone, ma la nostra classe implementa Cloneable, quindi l'eccezione è irraggiungibile.

Copia superficiale: cosa fa davvero

Una copia superficiale duplica i campi immediati. I riferimenti all'interno dell'oggetto vengono copiati come riferimenti, non come nuovi oggetti — quindi l'originale e il clone condividono tutto ciò a cui puntano quei riferimenti:

public class Person implements Cloneable {
  String name;
  int[]  scores;

  @Override
  public Person clone() {
    try { return (Person) super.clone(); }
    catch (CloneNotSupportedException e) { throw new AssertionError(e); }
  }
}

Person a = new Person();
a.scores = new int[]{1, 2, 3};
Person b = a.clone();
b.scores[0] = 99;
System.out.println(a.scores[0]);   // 99 — they share the same array

Per i primitivi e i valori immutabili (String, Integer, LocalDate), la copia superficiale va bene. Per i sotto-oggetti mutabili, è quasi sempre sbagliata — modificare il clone si ripercuote sull'originale.

Copia profonda: la soluzione

Per ottenere una copia veramente indipendente, esegui l'override di clone() per copiare ricorsivamente i campi mutabili:

@Override
public Person clone() {
  try {
    Person copy   = (Person) super.clone();
    copy.scores   = scores.clone();          // arrays have their own clone()
    return copy;
  } catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
  }
}

Gli array implementano Cloneable nativamente e si copiano con .clone(). Le Collection no — per un campo List<String> scriveresti copy.items = new ArrayList<>(items); (vedi ArrayList). Per un grafo di tuoi tipi mutabili, ogni tipo nel grafo deve partecipare.

Perché Cloneable ha una cattiva reputazione

Alcune stranezze rendono clone() scomodo:

  • È un'interfaccia marcatore senza metodo clone()Cloneable di per sé non espone nulla; il contratto risiede su Object.clone().
  • Bypassa i costruttori — i campi del nuovo oggetto vengono riempiti dalla JVM, quindi eventuali invarianti che applichi nel tuo costruttore non vengono riverificati.
  • Le sottoclassi ereditano l'obbligo: se Parent fa l'override di clone(), ogni sottoclasse deve mantenere sincronizzata la logica di copia profonda, oppure eredita silenziosamente una versione superficiale non funzionante.
  • L'eccezione checked, il cast, la chiamata super.clone() — ogni override ripete lo stesso rumore.

L'alternativa moderna: copy constructor

Un copy constructor è semplicemente un costruttore che accetta un'istanza della stessa classe e ne copia i campi:

public class Person {
  String name;
  int[]  scores;

  public Person(Person other) {
    this.name   = other.name;
    this.scores = other.scores.clone();   // deep where it matters
  }
}

Person b = new Person(a);

Viene eseguito attraverso il normale costruttore, quindi gli invarianti vengono verificati. È Java puro — nessuna interfaccia marcatore, nessuna CloneNotSupportedException, nessun cast. Le sottoclassi scrivono semplicemente il proprio copy constructor che chiama super(other). La raccomandazione di Effective Java è di preferire i copy constructor (o le factory statiche copyOf) rispetto a clone.

Le classi simili alle Collection seguono già questo pattern: new ArrayList<>(other), new HashMap<>(other), Set.copyOf(other).

Quale approccio dovrei usare?

SituazioneApproccio consigliato
Classe nuova e semplice che controlli tuCopy constructor o factory statica copyOf
Classe con soli campi primitivi o immutabiliQualsiasi — anche un clone() superficiale è sicuro
Classe con campi mutabili (liste, array, oggetti annidati)Copy constructor con copie profonde esplicite
API esistente che richiede già CloneableFai l'override di clone() e copia in profondità i campi mutabili
Tipo valore che puoi riprogettareRendilo immutabile — così nessuna copia è necessaria

In breve: preferisci un copy constructor per il codice nuovo, e ricorri a clone() solo quando un contratto esistente ti obbliga.

Record e tipi immutabili

I Record sono immutabili, quindi non hanno bisogno di clonazione — condividi lo stesso riferimento ovunque. Se hai bisogno di una copia modificata, scrivi piccoli metodi with...:

record Point(int x, int y) {
  Point withX(int newX) { return new Point(newX, y); }
}

Questo stile — "costruisci una nuova istanza con un campo cambiato" — è di solito più chiaro della clonazione seguita da mutazione.

Un esempio pratico

java— editable, runs on the server

Cosa c'è dopo

La maggior parte dei problemi legati alla clonazione scompare se la classe è immutabile fin dall'inizio — niente da copiare in modo difensivo, nessuna sorpresa di aliasing, sicura da condividere tra thread. Il prossimo capitolo illustra come progettare una classe in questo modo. Continua con Classi immutabili in Java.

Esercitazione

Pratica
Cosa fa il metodo predefinito `Object.clone()` a un campo che contiene una lista mutabile?
Cosa fa il metodo predefinito `Object.clone()` a un campo che contiene una lista mutabile?
Was this page helpful?