Domanda

Ho bisogno di implementare un clone profonda in uno dei miei oggetti che non ha alcuna superclasse.

Qual è il modo migliore per gestire la CloneNotSupportedException controllato generata dalla superclasse (che è Object)?

Un collega mi ha consigliato di gestire la cosa seguente modo:

@Override
public MyObject clone()
{
    MyObject foo;
    try
    {
        foo = (MyObject) super.clone();
    }
    catch (CloneNotSupportedException e)
    {
        throw new Error();
    }

    // Deep clone member fields here

    return foo;
}

Questa sembra una buona soluzione per me, ma ho voluto buttare fuori alla comunità StackOverflow per vedere se ci sono altre intuizioni che posso comprendere. Grazie!

È stato utile?

Soluzione

Hai assolutamente necessario utilizzare clone? La maggior parte delle persone concordano sul fatto che clone di Java è rotto.

Josh Bloch sul Design - Copia Constructor contro clonazione

  

Se avete letto l'articolo sulla clonazione nel mio libro, soprattutto se si legge tra le righe, si sa che penso clone è profondamente rotto. [...] E 'un peccato che Cloneable è rotto, ma succede.

Si può leggere di più discussione sul tema nel suo libro Effective Java 2nd Edition, Articolo 11: L'override clone giudiziosamente . Egli raccomanda invece di utilizzare un costruttore di copia o di copia di fabbrica.

Ha continuato a scrivere pagine di pagine su come, se si sente è necessario, è necessario implementare clone. Ma lui si è chiuso con questo:

  

è tutto questo complessità davvero necessario? Raramente. Se si estende una classe che implementa Cloneable, avete poca scelta, ma per implementare un metodo clone ben educati. In caso contrario, si sta meglio fornire mezzi alternativi di copia di un oggetto, o semplicemente non fornendo la capacità .

L'accento è stato il suo, non mio.


Dal momento che messo in chiaro che si ha poca scelta ma per attuare clone, ecco cosa si può fare in questo caso: assicurarsi che MyObject extends java.lang.Object implements java.lang.Cloneable. Se questo è il caso, allora si può garantire che si Mai prendere un CloneNotSupportedException. Lanciare AssertionError come qualcuno ha suggerito sembra ragionevole, ma è anche possibile aggiungere un commento che spiega perché non sarà mai entrato nel blocco catch in questo caso particolare .


In alternativa, come altri hanno suggerito, si può forse implementare clone senza chiamare super.clone.

Altri suggerimenti

A volte è più semplice da implementare un costruttore di copia:

public MyObject (MyObject toClone) {
}

E 'consente di risparmiare la fatica di gestire CloneNotSupportedException, funziona con campi final e non devi preoccuparti per il tipo di tornare.

Il modo in cui il codice funziona è abbastanza vicino al modo in cui "canonica" per scriverlo. Mi piacerebbe gettare un AssertionError all'interno della cattura, però. Segnala che quella linea non dovrebbe mai essere raggiunto.

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}

Ci sono due casi in cui sarà gettato la CloneNotSupportedException:

  1. La classe di essere clonato non implementato Cloneable (supponendo che la clonazione effettiva alla fine rimanda metodo clone di per Object). Se la classe che si sta scrivendo questo metodo in attrezzi Cloneable, questo non accadrà mai (dal momento che eventuali sottoclassi erediteranno in modo appropriato).
  2. L'eccezione è espressamente lanciata da un'implementazione - questo è il modo consigliato per prevenire clonabilità in una sottoclasse quando la superclasse è Cloneable
  3. .

Il secondo caso non può verificarsi nella classe (come si sta chiamando direttamente il metodo della superclasse nel blocco try, anche se richiamato da una chiamata super.clone() sottoclasse) e l'ex non dovrebbe in quanto la classe chiaramente dovrebbe attuare Cloneable.

In sostanza, si dovrebbe registrare l'errore di sicuro, ma in questo caso particolare sarà solo accadere se si rovinare il vostro definizione della classe. Così trattarla come una versione verificata di NullPointerException (o simili) -. Non sarà mai gettato se il codice è funzionale


In altre situazioni si avrebbe bisogno di essere preparati per questa eventualità - non v'è alcuna garanzia che un dato oggetto è clonabile, in modo che quando cattura l'eccezione si dovrebbe prendere i provvedimenti opportuni in base a questa condizione (continua con l'oggetto esistente, prendere una strategia di clonazione un'alternativa ad esempio serialize-deserializzare, lanciare un IllegalParameterException se il metodo richiede il parametro da clonabile, ecc, ecc.).

Modifica : Anche se nel complesso Vorrei sottolineare che sì, clone() è davvero difficile da implementare in modo corretto e difficile per i chiamanti di sapere se il valore di ritorno sarà quello che vogliono, doppiamente così se si considera profonda vs cloni poco profonde. Spesso è meglio solo per evitare il tutto interamente ed utilizzare un altro meccanismo.

serializzazione di fare copie profonde. Questa non è la soluzione più veloce, ma non dipende dal tipo.

È possibile implementare costruttori di copia protetti in questo modo:

/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
    this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
    this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
    // etc
}

public MyObject clone() {
    return new MyObject(this);
}
public class MyObject implements Cloneable, Serializable{   

    @Override
    @SuppressWarnings(value = "unchecked")
    protected MyObject clone(){
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            ByteArrayOutputStream bOs = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bOs);
            oos.writeObject(this);
            ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
            return  (MyObject)ois.readObject();

        } catch (Exception e) {
            //Some seriouse error :< //
            return null;
        }finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {

                }
            if (ois != null)
                try {
                    ois.close();
                } catch (IOException e) {

                }
        }
    }
}

Per quanto la maggior parte delle risposte qui sono validi, ho bisogno di dire che la soluzione è anche come gli sviluppatori effettivi Java API fanno. (O Josh Bloch o Neal GDopo)

Ecco un estratto da OpenJDK, classe ArrayList:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

Come avete notato e gli altri menzionati, CloneNotSupportedException ha quasi nessuna possibilità di essere gettato se hai dichiarato che si implementa l'interfaccia Cloneable.

Inoltre, non c'è bisogno di voi per l'override del metodo, se non fai nulla di nuovo nel metodo override. Hai solo bisogno di ignorare che quando hai bisogno di fare operazioni aggiuntive sull'oggetto o è necessario renderlo pubblico.

In definitiva, è ancora meglio per evitarlo e farlo utilizzando qualche altro modo.

Il fatto che l'implementazione Java di Cloneable è rotto non significa non è possibile creare uno proprio.

Se vero scopo OP era quello di creare un clone profondo, penso che sia possibile creare un'interfaccia simile a questo:

public interface Cloneable<T> {
    public T getClone();
}

quindi utilizzare il costruttore prototipo accennato prima per la sua attuazione:

public class AClass implements Cloneable<AClass> {
    private int value;
    public AClass(int value) {
        this.vaue = value;
    }

    protected AClass(AClass p) {
        this(p.getValue());
    }

    public int getValue() {
        return value;
    }

    public AClass getClone() {
         return new AClass(this);
    }
}

e un'altra classe con un campo di oggetto AClass:

public class BClass implements Cloneable<BClass> {
    private int value;
    private AClass a;

    public BClass(int value, AClass a) {
         this.value = value;
         this.a = a;
    }

    protected BClass(BClass p) {
        this(p.getValue(), p.getA().getClone());
    }

    public int getValue() {
        return value;
    }

    public AClass getA() {
        return a;
    }

    public BClass getClone() {
         return new BClass(this);
    }
}

In questo modo è possibile easely profondo clone un oggetto della classe BClass senza bisogno di @SuppressWarnings o altro codice ingannevole.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top