Perché le persone hanno così paura di usare clone () (sulle classi di raccolta e JDK)?

Un certo numero di volte ho sostenuto che l’uso di clone() non è una ctriggers pratica. Sì, conosco gli argomenti. Bloch ha detto che è cattivo. Lo ha fatto, ma ha detto che implementare il clone() è cattivo. D’altra parte l’uso del clone, specialmente se implementato correttamente da una libreria attendibile, come il JDK, è OK.

Proprio ieri ho avuto una discussione su una mia risposta che suggerisce semplicemente che l’uso di clone() per ArrayList è OK (e non ho ottenuto alcun upvoting per questo motivo, immagino).

Se guardiamo @author di ArrayList , possiamo vedere un nome familiare – Josh Bloch. Quindi clone() su ArrayList (e altre raccolte) è perfettamente a posto (basta osservare le loro implementazioni).

Lo stesso vale per Calendar e forse la maggior parte delle classi java.lang e java.util .

Quindi, dammi una ragione per non usare clone() con le classi JDK?

Quindi, dammi una ragione per non usare clone () con le classi JDK?

  • Dato un riferimento ArrayList , è necessario un controllo getClass per verificare che non sia una sottoclass della class JDK. E poi cosa? Non ci si può fidare di potenziali sottoclassi. Presumibilmente una sottoclass avrebbe in qualche modo un comportamento diverso.
  • Richiede che il riferimento sia più specifico di List . Ad alcune persone non importa, ma l’opinione della maggioranza è che questa è una ctriggers idea.
  • Dovrai affrontare un cast, un cast pericoloso.

Dalla mia esperienza, il problema di clone () sorge su classi derivate.

Diciamo che ArrayList implementa clone (), che restituisce un object di ArrayList .

Supponi che ArrayList abbia una class derivata, ovvero MyArrayList . Sarà un disastro se MyArrayList non sovrascrive il metodo clone (). (Di default eredita il codice da ArrayList ).

L’utente di MyArrayList può aspettarsi clone () per restituire un object di MyArrayList ; tuttavia, questo non è vero.

Questo è fastidioso: se una class base implementa clone (), la sua class derivata deve sovrascrivere il clone () fino in fondo.

Risponderò con una citazione dall’uomo stesso ( Josh Bloch su Design – Copy Constructor versus Cloning ):

Ci sono pochissime cose per le quali uso più Cloneable . Spesso fornisco un metodo di public clone su lezioni concrete perché la gente se lo aspetta.

Non può essere più esplicito di questo: clone() sulle sue classi Collection Framework vengono fornite perché la gente se lo aspetta . Se la gente smettesse di aspettarlo, l’avrebbe buttato via volentieri. Un modo per convincere la gente a smettere di aspettarsi è educare le persone a smettere di usarlo, e non a difenderne l’uso.

Naturalmente, lo stesso Bloch ha anche detto (non la citazione esatta ma chiusa) “API è come il sesso: fai un errore e lo sostieni per tutta la vita”. Probabilmente nessun public clone() può mai essere restituito. Tuttavia, non è un buon motivo per usarlo.

Per non incoraggiare altri sviluppatori meno esperti a implementare Clone() . Ho lavorato con molti sviluppatori i cui stili di codifica sono in gran parte copiati dal codice (a volte terribile) con cui hanno lavorato.

Non impone se l’implementatore eseguirà una copia profonda o superficiale.

Usare il clone può essere rischioso molto rischioso se non si controlla l’implementazione di questo metodo di clonazione … come si può supporre che un implone clone possa agire in modi diversi … clone superficiale o profondo … e alcuni sviluppatori potrebbero non sempre controlla quale tipo di clone recupererà …

In una grande applicazione, una grande squadra, è anche rischioso perché quando si usa la clonazione, se si modifica l’implementazione del clone si può modificare il comportamento dell’applicazione e creare nuovi bug … e controllare ovunque il clone è stato chiamato (ma può essere lo stesso vale per altri metodi di oggetti come equals, toString …

Quando si modifica il clone di una sottoclass di piccole dimensioni A (dal clone di Deep a Shallow per esempio), se un’istanza di B ha un riferimento a A e un impl di clone profondo, allora poiché gli oggetti referenziati in A non sono clonati superficialmente, il clone B non sarà più un clone profondo (ed è lo stesso per ogni class che fa riferimento a un’istanza B …). Non è facile gestire la profondità dei tuoi metodi di clonazione.

Anche quando hai un’interfaccia (estendibile Clonable) e molte (molte!) Implementazioni, a volte controllerai alcuni impl e vedrai che i cloni sono profondi e chiami un clone sull’interfaccia ma non puoi essere sicuro al momento dell’esecuzione avere un clone profondo e può introdurre bug …

Pensa che potrebbe essere meglio impiantare per ogni metodo un metodo shallowClone e deepClone, e su esigenze molto specifiche implementare metodi personalizzati (ad esempio vuoi un clone e limitare la profondità di questo clone a 2, crea un impl personalizzato per quello in classi interessate).

Non pensare che sia un gran problema utilizzare un metodo clone su una class JDK poiché non verrà modificato da un altro sviluppatore, o almeno non spesso. Ma è meglio non chiamare clone sulle classi JDK se non si conosce l’implementazione della class in fase di compilazione. Voglio dire, chiamare il clone su ArrayList non è un problema, ma chiamarlo su una collezione potrebbe essere pericoloso poiché un’altra implementazione di Collection potrebbe essere introdotta da un altro sviluppatore (può persino estendere un impl di Collection) e nulla ti dice che il clone di questa impl funzionerà come ti aspetti che faccia …

Se i tipi nella tua collezione sono mutabili, devi preoccuparti se solo la raccolta, da sola, sarà clonata, o se gli elementi saranno anche clonati … quindi implementando la tua funzione, dove sai se gli elementi o solo il contenitore sarà clonato renderà le cose molto più chiare.