Informazioni su Java clonabile


95

Stavo cercando alcuni tutorial che spiegassero Java Cloneable, ma non ho ottenuto buoni collegamenti e Stack Overflow sta diventando comunque una scelta più ovvia.

Vorrei sapere quanto segue:

  1. Cloneablesignifica che possiamo avere un clone o una copia di oggetti, implementando l' Cloneableinterfaccia. Quali sono i vantaggi e gli svantaggi di farlo?
  2. Come avviene la clonazione ricorsiva se l'oggetto è un oggetto composto?

2
I vantaggi e gli svantaggi rispetto a cosa?
Galactus

4
Ho letto che significa vantaggio di una classe clonabile rispetto a non. Non sono sicuro di come altro potrebbe essere interpretato: S
allyourcode

Risposte:


159

La prima cosa che dovresti sapere Cloneableè: non usarlo.

È molto difficile implementare la clonazione con il Cloneablediritto e lo sforzo non ne vale la pena.

Invece di utilizzare alcune altre opzioni, come apache-commons SerializationUtils(deep-clone) o BeanUtils(shallow-clone), o semplicemente utilizzare un costruttore di copie.

Vedi qui per le opinioni di Josh Bloch sulla clonazione con Cloneable, che spiega i molti svantaggi dell'approccio. ( Joshua Bloch era un dipendente Sun e ha guidato lo sviluppo di numerose funzionalità Java.)


1
Ho collegato le parole di Bloch (invece di citarle)
Bozho

3
Nota che Block dice di non usare Cloneable. Non dice di non usare la clonazione (o almeno spero di no). Esistono molti modi per implementare semplicemente la clonazione che sono molto più efficienti delle classi come SerializationUtils o BeanUtils che usano la reflection. Vedi il mio post qui sotto per un esempio.
Charles,

qual è l'alternativa a un costruttore di copie quando si definiscono le interfacce? aggiungi semplicemente un metodo di copia?
benez

@benez direi di sì. da java-8 puoi avere staticmetodi nelle interfacce, quindi basta fornire un static WhatEverTheInterface copy(WhatEverTheInterface initial)? ma mi chiedo cosa ti dia, dal momento che copi i campi da un oggetto durante la clonazione, ma un'interfaccia definisce solo metodi. ti va di spiegare?
Eugene

40

Cloneable stesso è purtroppo solo un'interfaccia marker, cioè: non definisce il metodo clone ().

Ciò che fa è modificare il comportamento del metodo Object.clone () protetto, che genererà un'eccezione CloneNotSupportedException per le classi che non implementano Cloneable ed eseguirà una copia superficiale a livello di membro per le classi che lo fanno.

Anche se questo è il comportamento che stai cercando, dovrai comunque implementare il tuo metodo clone () per renderlo pubblico.

Quando si implementa il proprio clone (), l'idea è di iniziare con l'oggetto creato da super.clone (), che è garantito essere della classe corretta, e quindi eseguire qualsiasi popolazione aggiuntiva di campi nel caso in cui una copia superficiale non sia ciò che tu vuoi. Chiamare un costruttore da clone () sarebbe problematico in quanto interromperebbe l'ereditarietà nel caso in cui una sottoclasse desideri aggiungere la propria logica clonabile aggiuntiva; se chiamasse super.clone () in questo caso otterrebbe un oggetto della classe sbagliata.

Questo approccio ignora qualsiasi logica che può essere definita nei tuoi costruttori, che potrebbe essere potenzialmente problematica.

Un altro problema è che qualsiasi sottoclasse che dimentica di sovrascrivere clone () erediterà automaticamente la copia superficiale predefinita, che probabilmente non è ciò che si desidera in caso di stato mutabile (che ora sarà condiviso tra l'origine e la copia).

La maggior parte degli sviluppatori non usa Cloneable per questi motivi e implementa semplicemente un costruttore di copie.

Per ulteriori informazioni e potenziali insidie ​​di Cloneable, consiglio vivamente il libro Effective Java di Joshua Bloch


12
  1. La clonazione invoca un modo extra-linguistico di costruire oggetti, senza costruttori.
  2. La clonazione richiede di trattare in qualche modo con CloneNotSupportedException o di disturbare il codice client per il trattamento.
  3. I vantaggi sono piccoli: non è necessario scrivere manualmente un costruttore di copia.

Quindi, usa Cloneable con giudizio. Non ti dà benefici sufficienti rispetto allo sforzo di cui hai bisogno per fare domanda per fare tutto bene.


Come ha detto Bozho, non usare Cloneable. Usa invece un costruttore di copie. javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane, e se non conoscessi il tipo di oggetto da clonare, come sapresti quale costruttore di copia della classe richiamare?
Steve Kuo

@ Steve: io non seguo. Se hai intenzione di clonare un oggetto, presumo che tu sappia già di che tipo è - dopotutto, hai in mano l'oggetto che stai pianificando di clonare. E se c'è una situazione in cui il tuo oggetto ha perso il suo tipo specifico per uno più generico, non potresti valutarlo usando una semplice 'istanza di' ???
Bane

4
@Bane: Supponi di avere un elenco di oggetti tutti derivati ​​dal tipo A, magari con 10 tipi diversi. Non sai qual è il tipo di ogni oggetto. Usare instanceof in questo caso è una pessima idea. Se aggiungi un altro tipo, ogni volta che lo fai dovresti aggiungere un'altra istanza di test. E se le classi derivate si trovano in un altro pacchetto a cui non puoi nemmeno accedere? La clonazione è un modello comune. Sì, l'implementazione java è pessima, ma ci sono molti modi per aggirarla che funzioneranno perfettamente. Un costruttore di copie non è un'operazione equivalente.
Charles

@Charles: In assenza di un esempio dettagliato e in mancanza di esperienza recente nell'affrontare questo tipo di problema, dovrò rimandare a Bloch. Articolo # 11. È lungo e un po 'difficile da leggere, ma in pratica dice "evita la clonazione ogni volta che puoi, i costruttori di copie sono tuoi amici".
Bane

7

La clonazione è un paradigma di programmazione di base. Il fatto che Java possa averlo implementato male in molti modi non diminuisce affatto la necessità di clonazione. Ed è facile implementare la clonazione che funzionerà come vuoi che funzioni, superficiale, profonda, mista, qualunque cosa. Puoi anche usare il nome clone per la funzione e non implementare Cloneable, se lo desideri.

Supponiamo che io abbia le classi A, B e C, dove B e C derivano da A. Se ho un elenco di oggetti di tipo A come questo:

ArrayList<A> list1;

Ora, quell'elenco può contenere oggetti di tipo A, B o C. Non sai che tipo sono gli oggetti. Quindi, non puoi copiare l'elenco in questo modo:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Se l'oggetto è effettivamente di tipo B o C, non otterrai la copia corretta. E se A fosse astratto? Ora, alcune persone hanno suggerito questo:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Questa è un'idea molto, molto cattiva. Cosa succede se aggiungi un nuovo tipo derivato? Cosa succede se B o C sono in un altro pacchetto e non hai accesso a loro in questa classe?

Quello che vorresti fare è questo:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Molte persone hanno indicato perché l'implementazione Java di base di clone è problematica. Ma è facilmente superabile in questo modo:

In classe A:

public A clone() {
    return new A(this);
}

In classe B:

@Override
public B clone() {
    return new B(this);
}

In classe C:

@Override
public C clone() {
    return new C(this):
}

Non sto implementando Cloneable, sto solo usando lo stesso nome di funzione. Se non ti piace, chiamalo qualcos'altro.


Ho appena visto questo dopo aver risposto al tuo commento in una risposta separata; Vedo dove stai andando ora, tuttavia 2 cose: 1) l'OP ha chiesto specificamente sull'uso di Cloneable (non sul concetto generico di clonazione) e 2) stai dividendo un po 'i capelli qui nel tentativo di distinguere tra un costruttore di copie e il concetto generico di clonazione. L'idea che trasmetti qui è valida, ma alla radice stai usando solo un costruttore di copie. ;)
Bane

Anche se voglio dire che sono d'accordo con il tuo approccio qui, quello di includere un A # copyMethod (), piuttosto che costringere l'utente a chiamare direttamente il costruttore di copie.
Bane

5

A) Non ci sono molti vantaggi del clone rispetto a un costruttore di copie. Probabilmente il più grande è la capacità di creare un nuovo oggetto dello stesso identico tipo dinamico (assumendo che il tipo dichiarato sia clonabile e abbia un metodo clone pubblico).

B) Il clone predefinito crea una copia superficiale e rimarrà una copia superficiale a meno che l'implementazione del clone non la modifichi. Questo può essere difficile, soprattutto se la tua classe ha i campi finali

Bozho ha ragione, il clone può essere difficile da ottenere. Un produttore / fabbrica di copie soddisferà la maggior parte delle esigenze.


0

Quali sono gli svantaggi di Cloneable?

La clonazione è molto pericolosa se l'oggetto che stai copiando ha una composizione, devi pensare di seguito ai possibili effetti collaterali in questo caso perché il clone crea una copia superficiale:

Supponiamo che tu abbia un oggetto per gestire la manipolazione relativa a db. Diciamo, quell'oggetto ha Connectionoggetto come una delle proprietà.

Così, quando qualcuno crea clone originalObject, l'oggetto viene creato, diciamo, cloneObject. Qui il originalObjecte cloneObjectmantiene lo stesso riferimento per l' Connectionoggetto.

Diciamo che originalObjectchiude l' Connectionoggetto, quindi ora cloneObjectnon funzionerà perché l' connectionoggetto è stato condiviso tra di loro ed è stato effettivamente chiuso da originalObject.

Un problema simile può verificarsi se si supponga di voler clonare un oggetto che ha IOStream come proprietà.

Come avviene la clonazione ricorsiva se l'oggetto è un oggetto composto?

Clonabile esegue una copia superficiale. Il significato è che i dati dell'oggetto originale e dell'oggetto clone punteranno allo stesso riferimento / memoria. contrariamente nel caso di copia profonda, i dati dalla memoria dell'oggetto originale vengono copiati nella memoria dell'oggetto clone.


Il tuo ultimo paragrafo è molto confuso. Cloneablenon esegue una copia, Object.clonefa. "I dati dalla memoria dell'oggetto originale vengono copiati nella memoria dell'oggetto clone" è esattamente ciò che Object.clonefa. È necessario parlare della memoria degli oggetti referenziati per descrivere la copia profonda.
aioobe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.