Clonar (método Java)
clone()
es un método del lenguaje de programación Java para la duplicación de objetos. En Java, los objetos se manipulan a través de variables de referencia y no existe ningún operador para copiar un objeto: el operador de asignación duplica la referencia, no el objeto. El método clone() proporciona esta funcionalidad faltante.
Sinopsis
Las clases que desean copiar la función deben implementar algún método para hacerlo. Hasta cierto punto, esa función la proporciona "Object.clone()
".
clone()
actúa como un constructor de copias. Normalmente, llama al método clone()
de su superclase para obtener la copia, etc., hasta que finalmente llega al método clone()
de Object
. El método especial clone()
de la clase base Object
proporciona un mecanismo estándar para duplicar objetos.
El método clone()
de la clase Object
crea y devuelve una copia del objeto, con la misma clase y con todos los campos con los mismos valores. Sin embargo, Object.clone()
lanza una CloneNotSupportedException
a menos que el objeto sea una instancia de una clase que implemente la interfaz de marcador Cloneable
.
La implementación predeterminada de Object.clone()
realiza una copia superficial. Cuando una clase desea una copia profunda o algún otro comportamiento personalizado, debe implementarlo en su propio método clone()
después de obtener la copia de la superclase.
La sintaxis para llamar a clone
en Java es (asumiendo que obj
es una variable de un tipo de clase que tiene un método clone()
público):
Objeto Copia = obj.clone();
o comúnmente
MyClass Copia = ()MyClass) obj.clone();
que proporciona la conversión de tipos necesaria para asignar la referencia general Object
devuelta desde clone
a una referencia a un objeto MyClass
.
Una desventaja del diseño del método clone()
es que el tipo de retorno de clone()
es Object
y debe convertirse explícitamente al tipo apropiado. Sin embargo, es preferible anular clone()
para que devuelva el tipo apropiado y elimina la necesidad de convertir en el cliente (usando tipos de retorno covariantes, desde J2SE 5.0).
Otra desventaja es que a menudo no se puede acceder al método clone()
en un tipo abstracto. La mayoría de las interfaces y clases abstractas en Java no especifican un método clone()
público. Como resultado, a menudo el método clone()
solo se puede utilizar si se conoce la clase real de un objeto, lo que es contrario al principio de abstracción de utilizar el tipo más genérico posible. Por ejemplo, si uno tiene una referencia List
en Java, no se puede invocar clone()
en esa referencia porque List
no especifica ningún método clone()
público. Las implementaciones reales de List
como ArrayList
y LinkedList
generalmente tienen métodos clone()
, pero es inconveniente y una mala abstracción llevar consigo el tipo de clase real de un objeto.
Alternativas
Existen alternativas a clone()
, en particular el uso de un constructor de copia (un constructor que acepta como parámetro otra instancia de la misma clase) o un método de fábrica. Estos métodos no siempre son adecuados cuando no se conoce de antemano el tipo concreto del objeto clonado. (Sin embargo, clone()
tampoco suele ser adecuado por la misma razón, ya que la mayoría de las clases abstractas no implementan un método clone()
público).
El uso de serialización y deserialización también es una alternativa al uso de clones.
Patrón de Singleton
Al escribir una clase utilizando el patrón Singleton, solo puede existir una instancia de esa clase a la vez. Como resultado, no se debe permitir que la clase realice un clon. Para evitarlo, se puede anular el método clone()
utilizando el siguiente código:
público Objeto clone() lanzamientos CloneNotSupportedException {} tiro nuevo CloneNotSupportedException();}
Esto solo es necesario si una superclase implementa un método clone()
público o para evitar que una subclase utilice el método clone()
de esta clase para obtener una copia. Las clases normalmente no heredan un método clone()
público porque Object
no tiene un método clone()
público, por lo que normalmente no es necesario implementar explícitamente un método clone()
no funcional.
jerarquía de clase
Para proporcionar un objeto clonable de cualquier tipo, el método clone() debe estar declarado e implementado correctamente de acuerdo con la convención descrita en Object.clone().
1) Cada tipo que necesite ser clonado debe tener un método clone() público en su propia clase o un método clone() de acceso público en una de sus clases padre.
Ejemplo:
Para invocar clone() en varY1, que es del tipo Y, entonces Y o una clase padre de Y debe declarar un método clone() de acceso público. Aquí, es la clase padre X la que proporciona el método clone() público.
público clase X implementos Cloneable {} público X clone() lanzamientos CloneNotSupportedException {} Regreso ()X) super.clone(); }}público clase Y extensivos X {} }público clase Z extensivos Y {} }público clase test1 {} público vacío función() lanzamientos CloneNotSupportedException {} Y varY1 = nuevo Z(); Y vary2 = ()Y) varY1.clone(); }}
2) Cada clase que implemente clone() debe llamar a super.clone() para obtener la referencia del objeto clonado. Si la clase tiene referencias de objetos que también deben clonarse (cuando se realiza una copia profunda, por ejemplo), entonces el método clone() debe realizar las modificaciones necesarias en el objeto antes de devolverlo. (Dado que Object.clone() devuelve una copia exacta del objeto original, todos los campos mutables, como colecciones y matrices, se compartirían entre el original y la copia, lo que en la mayoría de los casos no sería ni esperado ni deseado).
Ejemplo:
Dado que la clase Z contiene una referencia de objeto, su método clone() también clona esa referencia de objeto para devolver una copia profunda del original.
público clase X implementos Cloneable {} público X clone() lanzamientos CloneNotSupportedException {} Regreso ()X) super.clone(); }}público clase Y extensivos X {} }público clase ObjectABC implementos Cloneable {} público ObjectABC clone() lanzamientos CloneNotSupportedException {} Regreso ()ObjectABC) super.clone(); }}público clase Z extensivos Y {} privado ObjectABC algunosABC; público Z clone() lanzamientos CloneNotSupportedException {} Z newZ = ()Z) super.clone(); newZ.algunosABC = algunosABC.clone(); Regreso newZ; }}público clase test1 {} público vacío función() lanzamientos CloneNotSupportedException {} Y varY1 = nuevo Z(); Y vary2 = ()Y) varY1.clone(); }}
Pitfalls
Si cada clase de una jerarquía implementa un método clone()
, se invocarán todas estas funciones al clonar, lo que agregará cierta sobrecarga. Con el paso de muchas iteraciones, esta sobrecarga podría volverse significativa.
Con gráficos de objetos complejos, la copia profunda también puede volverse problemática cuando existen referencias recursivas.
No siempre es adecuado tener varias copias del mismo objeto circulando. Si los consumidores no comprenden completamente el propósito de una implementación específica de clone()
, puede romper involuntariamente el paradigma de "un solo objeto, múltiples referencias".
Campos finales
Generalmente, clone()
es incompatible con los campos final
. Debido a que clone()
es esencialmente un constructor predeterminado (que no tiene argumentos), es imposible asignar un campo final
dentro de un método clone()
; el resultado es un error del compilador. Cuando el valor del campo es un objeto inmutable, esto está bien; simplemente deje que el "constructor" copie la referencia y tanto el original como su clon compartirán el mismo objeto.
Pero cuando el valor es un objeto mutable, debe copiarse en profundidad. Una solución es eliminar el modificador final
del campo, renunciando a los beneficios que confería el modificador.
Por esta razón, algunos programadores sugieren hacer que los objetos de la jerarquía sean serializables y crear copias serializando el objeto antiguo y luego creando un nuevo objeto a partir del flujo de bits resultante, lo que maneja correctamente los miembros de datos finales, pero es significativamente más lento.
Alternativamente, se puede devolver un objeto completamente nuevo a partir de los campos de objetos actuales, lo que se puede hacer primero llamando al constructor y luego asignando campos no finales. Otro método alternativo es hacer que la idea sea formal: crear un constructor de copia que tome una instancia. De hecho, eso es lo que algunas personas recomiendan en lugar de clonar.
Referencias
- ^ Miller, Dave (6 de agosto de 1999). "Java Tip 76: Una alternativa a la técnica de copia profunda". JavaWorld. Retrieved 2020-07-14.
- ^ Clone() vs Copy constructor- que se recomienda en java, StackOverflow
Enlaces externos
- McManus, Eamonn (4 de abril de 2007). "Cloning Java objects using serialization". Blog de Eamonn McManusJava.net. Archivado desde el original el 13 de agosto de 2010. Retrieved 2010-11-16.
- Bloch, Joshua (2008). Java eficaz: una guía de idiomas de programación. La serie Java (2a edición). Addison-Wesley. ISBN 978-0-321-35668-0.
- "Evite clon". Prácticas Java recolectadas. Hirondelle Systems. 2009. Retrieved 2009-07-31.
- "Objeto (Java Plataforma SE 6)". Java Platform Standard Ed. 6. Sun Microsystems, Inc. 2008. Retrieved 2009-07-31.
- Roulo, Mark (1 de enero de 1999). "Cómo evitar trampas y anular correctamente métodos de java.lang. Objeto". JavaWorld. Retrieved 2020-07-14. - Cubre los fundamentos de la aplicación del método clon.
- Tutorial de cierre de Java.