Hice una búsqueda rápida en Google sobre la implementación de clone () en Java y encontré: http://www.javapractices.com/topic/TopicAction.do?Id=71
Tiene el siguiente comentario:
Los constructores de copia y los métodos de fábrica estáticos proporcionan una alternativa a la clonación y son mucho más fáciles de implementar.
Todo lo que quiero hacer es hacer una copia en profundidad. Implementar clone () parece tener mucho sentido, pero este artículo altamente clasificado en Google me da un poco de miedo.
Estos son los problemas que noté:
Los constructores de copia no funcionan con genéricos.
Aquí hay un pseudocódigo que no se compilará.
public class MyClass<T>{
..
public void copyData(T data){
T copy=new T(data);//This isn't going to work.
}
..
}
Ejemplo 1: uso de un constructor de copia en una clase genérica.
Los métodos de fábrica no tienen nombres estándar.
Es muy bueno tener una interfaz para código reutilizable.
public class MyClass<T>{
..
public void copyData(T data){
T copy=data.clone();//Throws an exception if the input was not cloneable
}
..
}
Ejemplo 2: uso de clone () en una clase genérica.
Me di cuenta de que la clonación no es un método estático, pero ¿no sería necesario realizar copias profundas de todos los campos protegidos? Al implementar clone (), el esfuerzo adicional para lanzar excepciones en subclases no clonables me parece trivial.
¿Me estoy perdiendo de algo? Se agradecería cualquier información.
Respuestas:
Básicamente, el clon está roto . Nada funcionará fácilmente con genéricos. Si tiene algo como esto (abreviado para expresar el punto):
public class SomeClass<T extends Copyable> { public T copy(T object) { return (T) object.copy(); } } interface Copyable { Copyable copy(); }
Luego, con una advertencia del compilador, puede hacer el trabajo. Debido a que los genéricos se borran en tiempo de ejecución, algo que haga una copia tendrá una advertencia del compilador que generará una conversión.
No es evitable en este caso.. Es evitable en algunos casos (gracias, kb304) pero no en todos. Considere el caso en el que tiene que admitir una subclase o una clase desconocida que implementa la interfaz (como si estuviera iterando a través de una colección de copiables que no necesariamente generaban la misma clase).fuente
Copyable<T>
en la respuesta de kd304 tiene mucho más sentido de usar.También está el patrón Builder. Consulte Effective Java para obtener más detalles.
No entiendo tu evaluación. En un constructor de copias, conoce perfectamente el tipo, ¿por qué es necesario utilizar genéricos?
public class C { public int value; public C() { } public C(C other) { value = other.value; } }
Recientemente hubo una pregunta similar aquí .
public class G<T> { public T value; public G() { } public G(G<? extends T> other) { value = other.value; } }
Una muestra ejecutable:
public class GenTest { public interface Copyable<T> { T copy(); } public static <T extends Copyable<T>> T copy(T object) { return object.copy(); } public static class G<T> implements Copyable<G<T>> { public T value; public G() { } public G(G<? extends T> other) { value = other.value; } @Override public G<T> copy() { return new G<T>(this); } } public static void main(String[] args) { G<Integer> g = new G<Integer>(); g.value = 1; G<Integer> f = g.copy(); g.value = 2; G<Integer> h = copy(g); g.value = 3; System.out.printf("f: %s%n", f.value); System.out.printf("g: %s%n", g.value); System.out.printf("h: %s%n", h.value); } }
fuente
Java no tiene constructores de copia en el mismo sentido que C ++.
Puede tener un constructor que tome un objeto del mismo tipo como argumento, pero pocas clases lo admiten. (menos que el número que admite la clonación)
Para un clon genérico, tengo un método auxiliar que crea una nueva instancia de una clase y copia los campos del original (una copia superficial) usando reflejos (en realidad, algo así como reflejos pero más rápido)
Para una copia profunda, un enfoque simple es serializar el objeto y deserializarlo.
Por cierto: Mi sugerencia es usar objetos inmutables, entonces no necesitará clonarlos. ;)
fuente
String
por ejemplo, es un objeto inmutable y puede pasar un String sin tener que copiarlo.Creo que la respuesta de Yishai podría mejorarse, por lo que no podemos tener ninguna advertencia con el siguiente código:
public class SomeClass<T extends Copyable<T>> { public T copy(T object) { return object.copy(); } } interface Copyable<T> { T copy(); }
De esta manera, una clase que necesita implementar una interfaz copiable tiene que ser así:
public class MyClass implements Copyable<MyClass> { @Override public MyClass copy() { // copy implementation ... } }
fuente
interface Copyable<T extends Copyable>
que es lo más parecido a un tipo propio que puede codificar en Java.interface Copyable<T extends Copyable<T>>
, ¿verdad? ;)A continuación se muestran algunas desventajas debido a las cuales muchos desarrolladores no usan
Object.clone()
Object.clone()
método requiere que agreguemos mucha sintaxis a nuestro código, como implementarCloneable
interfaz, definirclone()
método y manejarCloneNotSupportedException
y finalmente llamarObject.clone()
y convertirlo en nuestro objeto.Cloneable
la interfaz carece declone()
método, es una interfaz de marcador y no tiene ningún método, y aún así necesitamos implementarla solo para decirle a JVM que podemos realizarclone()
en nuestro objeto.Object.clone()
está protegido por lo que tenemos que proporcionar el nuestroclone()
y llamar indirectamenteObject.clone()
desde él.Object.clone()
no invoca ningún constructor.clone()
método en una clase secundaria, por ejemploPerson
, todas sus superclases deberían definir elclone()
método en ellas o heredarlo de otra clase principal, de lo contrario lasuper.clone()
cadena fallará.Object.clone()
Admite solo copia superficial, por lo que los campos de referencia de nuestro objeto recién clonado aún contendrán objetos cuyos campos de nuestro objeto original contenían. Para superar esto, necesitamos implementarclone()
en cada clase que hace referencia a nuestra clase y luego clonarlos por separado en nuestroclone()
método como en el siguiente ejemplo.Object.clone()
porque los campos finales solo se pueden cambiar a través de constructores. En nuestro caso, si queremos que cadaPerson
objeto sea único por id, obtendremos el objeto duplicado si lo usamosObject.clone()
porqueObject.clone()
no llamará al constructor yid
no se puede modificar el campo finalPerson.clone()
.Los constructores de copias son mejores que
Object.clone()
porqueMás información sobre Clonación de Java: constructor de copias frente a clonación
fuente
Por lo general, clone () funciona en conjunto con un constructor de copia protegido. Esto se hace porque clone (), a diferencia de un constructor, puede ser virtual.
En un cuerpo de clase para Derivado de una superclase Base tenemos
class Derived extends Base { }
Entonces, en su forma más simple, agregaría a esto un constructor de copia virtual con el clon (). (En C ++, Joshi recomienda clonar como constructor de copia virtual).
protected Derived() { super(); } protected Object clone() throws CloneNotSupportedException { return new Derived(); }
Se vuelve más complicado si desea llamar a super.clone () como se recomienda y tiene que agregar estos miembros a la clase, puede intentar esto
final String name; Address address; /// This protected copy constructor - only constructs the object from super-class and /// sets the final in the object for the derived class. protected Derived(Base base, String name) { super(base); this.name = name; } protected Object clone() throws CloneNotSupportedException { Derived that = new Derived(super.clone(), this.name); that.address = (Address) this.address.clone(); }
Ahora, si es una ejecución, tienes
Base base = (Base) new Derived("name");
y luego lo hiciste
esto invocaría, clone () en la clase Derived (la de arriba), esto invocaría super.clone (), que puede o no estar implementado, pero se recomienda llamarlo. La implementación luego pasa la salida de super.clone () a un constructor de copia protegido que toma una Base y le pasa los miembros finales.
Ese constructor de copia luego invoca al constructor de copia de la superclase (si sabe que tiene uno) y establece las finales.
Cuando regresa al método clone (), establece los miembros no finales.
Los lectores astutos notarán que si tiene un constructor de copia en la Base, será llamado por super.clone () - y será llamado nuevamente cuando invoque al superconstructor en el constructor protegido, por lo que puede estar llamando al super constructor de copias dos veces. Con suerte, si está bloqueando recursos, lo sabrá.
fuente
Un patrón que puede funcionar para usted es la copia a nivel de bean. Básicamente, usa un constructor sin argumentos y llama a varios establecedores para proporcionar los datos. Incluso puede utilizar las distintas bibliotecas de propiedades de los beans para establecer las propiedades con relativa facilidad. Esto no es lo mismo que hacer un clon () pero para muchos propósitos prácticos está bien.
fuente
La interfaz Cloneable está rota, en el sentido de que es inútil pero la clonación funciona bien y puede conducir a un mejor rendimiento para objetos grandes (8 campos y más, pero luego fallará el análisis de escape). tan preferible usar el constructor de copia la mayor parte del tiempo. El uso de clonar en una matriz es más rápido que Arrays.copyOf porque se garantiza que la longitud será la misma.
más detalles aquí https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
fuente
Si uno no es 100% consciente de todas las peculiaridades de
clone()
, le aconsejo que se mantenga alejado. Yo no diría que estáclone()
roto. Yo diría: úselo solo cuando esté completamente seguro de que es su mejor opción. Un constructor de copias (o un método de fábrica, creo que eso realmente no importa) es fácil de escribir (tal vez largo, pero fácil), solo copia lo que quieres que se copie y copia de la forma en que quieres que se copien las cosas. Puede recortarlo según sus necesidades exactas.Además: es fácil depurar lo que sucede cuando llama a su constructor de copia / método de fábrica.
Y
clone()
no crea una copia "profunda" de su objeto fuera de la caja, asumiendo que quiere decir que no soloCollection
se copian las referencias (por ejemplo, a ). Pero lea más sobre lo profundo y lo superficial aquí: copia profunda, copia superficial, clonaciónfuente
Lo que le falta es que la clonación crea copias superficiales por defecto y por convención, y que hacer que cree copias profundas no es, en general, factible.
El problema es que realmente no se pueden crear copias profundas de gráficos de objetos cíclicos sin poder realizar un seguimiento de los objetos que se han visitado. clone () no proporciona tal seguimiento (ya que tendría que ser un parámetro para .clone ()) y, por lo tanto, solo crea copias superficiales.
Incluso si su propio objeto invoca .clone para todos sus miembros, aún no será una copia profunda.
fuente
SuperDuperList<T>
o un derivado del mismo, entonces la clonación debería producir una nueva instancia del mismo tipo que el que se clonó; debe estar separado del original, pero debe hacer referencia a los mismosT
, en el mismo orden que el original. Nada de lo que se haga en ninguna de las listas a partir de ese momento debería afectar las identidades de los objetos almacenados en la otra. No conozco ninguna "convención" útil que requiera una colección genérica para exhibir cualquier otro comportamiento.