Recientemente en un proyecto, necesitaba suministrar/crear una "copia" de un objeto a fin de pasarla como parámetro a un método garantizando asi que el objeto original no fuera modificado. A este proceso es lo que se llama clonación ('cloning') de objetos. En otras palabras la clonación es el proceso de duplicación de un objeto para que en memoria existan dos objetos idénticos en el mismo instante de tiempo.
Usualmente el objeto se clona directamente llamando al método:
-
miObjeto.miMetodo(miObjetoImportante.clone());
Al igual que en muchos lenguajes orientados-a-objeto, en Java los objetos son pasados por referencia. Esto implica que cualquier acción tomada por el método "llamado" afectará el objeto que tiene el método "que llamó", esto debido a que ambos objetos son el mismo.
La clase java.lang.Object contiene una implementación native y protected del método clone. Esta implementación (que depende de la máquina sobre la que se ejecute el código) determina cuanta memoria está siendo usada por el objeto a ser clonado, reserva la misma cantidad de memoria para el objeto clon, y copia los valores de memoria de la vieja dirección de memoria a la nueva. Y al final se devuelve un java.lang.Object el cual es la referencia al nuevo objeto (el clon).
Para implementar la clonación en una clase, se deben hacer dos cosas:
- La clase debe implementar la interfaz
java.lang.Cloneable, esta interfaz no tiene métodos que implementar. El propósito deCloneablees indicar al método clone dejava.lang.Objectque el programador ha dado permiso explícito a la clase para permitir que los objetos instanciados a partir de ella sean clonados. - El método
clonede la clasejava.lang.Objectdebe ser sobreescrito con un acceso de tipo public en vez de protected. Es en este método que se implementará el código que clona del objeto. Un ejemplo de una implementación sencilla declonees:
-
{
-
Object clone = null;
-
try
-
{
-
clone = super.clone();
-
}
-
{
-
// No deberia suceder
-
}
-
return clone;
-
}
La excepción java.lang.CloneNotSupportedException es arrojada por el método clone de la clase java.lang.Object para prevenir que la operación de clonación se ejecute si no se ha otorgado el permiso para ello (es decir, se implemente la interfaz Cloneable).
En términos sencillos el método clone de la clase java.lang.Object crea un nuevo objeto mediante la copia exacta de los bytes de memoria y devolviendo una referencia, de esto se tiene que los objetos miembros de un clon apuntan a los mismos objetos que los objetos miembros del objeto original. Por ejemplo, consideremos la siguiente clase:
Cuando un objeto de la clase CloneTest es creado y a su atributo miembro se le asigna un valor, por ejemplo, "prueba", la memoria de este objeto realmente solo contiene los bits que representan una referencia a un objeto String que contiene "prueba". Cuando el método clone es ejecutado, el objeto es duplicado byte por byte y el clon contendrá una copia de la misma referencia que apunta al mismo objeto String que contiene "prueba". El resultado final es que existe solamente una copia de "prueba" y que una llamada sobre el objeto o su clon para modificar su valor resultará en cambios para ambos. Esto es conocido como 'shallow copy', donde solamente los miembros primitivos (y referencias a objetos) del objeto que están completamente contenidos en la memoria del objeto son duplicados, pero el resto de los objetos miembros en el objeto no lo son.
Este enfoque no es suficientemente bueno para algunos casos, ya que en general los objetos no están compuestos solamente de tipos primitivos. String, HashMap, ArrayList y otras clases que residen en el objeto a ser clonado, necesitan a su vez ser clonados, para que el proceso de clonación sea efectivo, a esto se le conoce como clonación "profunda" ('deep cloning'). A continuación un ejemplo de esto:
-
import java.util.HashMap;
-
import java.util.ArrayList;
-
-
{
-
{
-
Object clone = null;
-
try
-
{
-
clone = super.clone();
-
}
-
{
-
// No debería ocurrir
-
}
-
// Implementacion de la clonación profunda
-
return clone;
-
}
-
-
private ArrayList mArrayList;
-
-
{
-
return mArrayList;
-
}
-
-
{
-
mArrayList = pArrayList;
-
}
-
-
private HashMap mHashMap;
-
-
{
-
return mHashMap;
-
}
-
-
{
-
mHashMap = pHashMap;
-
}
-
-
private String mString;
-
-
-
{
-
return mString;
-
}
-
-
{
-
mString = pString;
-
}
-
-
{
-
return getClass() + " " + hashCode() +
-
"\n mArrayList=" + mArrayList+
-
"\n mHashMap=" + mHashMap +
-
"\n mString=" + mString;
-
}
-
}
A pesar que la clonación puede ser necesaria, tambien existen situaciones (usualmente de seguridad) donde se desea prohibir la clonación de una clase, para esto existen varias alternativas, las dos más usuales son:
- Declarar la clase como
final. Haciendo esto se previene que se puedan definir subclases para esta clase, e impedir que cualquiera pueda llamar al métodoclonedesde dentro de la clase (usando el alcanceprotected). Si se usa esta alternativa para impedir la clonación se debe verificar que ninguna de las superclases se pueda clonar. En el caso que esto sea asi, se debe sobreescribir el métodocloneen la clasefinaly arrojar unaCloneNotSupportedException. A pesar que esta alternativa efectivamente impide la clonación, establece restricciones acerca de las capacidades de extender la clase, las cuales pueden no ser aceptables. - Implementar
Cloneabley sobreescribir el métodoclonecon un alcancepublic, y hacer que arroje unaCloneNotSupportedException. A pesar que esto impide que la clase sea clonada, cualquier subclase puede hacer su propia implementación del métodoclonee implementar la clonación copiando los atributos uno por uno.
Existe otra forma (considerada como "oscura") de clonar un objeto, que es la serialización ('serializing') del objeto. La serialización usa un método nativo externo al objeto, es similiar a la clonación en el sentido en que, el estado binario del objeto es capturado desde memoria, pero en vez de copiarlo hacia otra dirección de memoria es copiado hacia un 'stream'. En un futuro post pienso conversar un poco acerca de la serialización.
Impecable!
Aparte de la muy buena explicacion sobre clone, me parece muy bueno que hayas mencionado sobre deep cloning, ya que es muy importante. :)
Programadores con falta de experiencia, normalmente tienen clases, que por decir, tienen dentro de ellas variables privadas que sean arreglo multidimensionales de tipo creado por el programador, y siempre que necesitan "clonar" toda la clase, lo implementan muy mal que tecnicamente ese malo.
¿Que necesidad hay de """clonar""" un String? (las ultra comillas son porque no es una clonación exactamente). Los Strings son objetos inmutables, lo que quiere decir que no se puede modificar su estado interno, en consecuencia es buena práctica compartirlos aún cuando estes haciendo 'deep copy'.
Muy buena la explicacion, pero tengo una duda al respecto:
Yo tengo dentro de una clase un ArrayList de un tipo creado por mi. ¿Como podria clonar un objeto de ese tipo? Es una cosa que tengo que hacer para el proyecto de fin de carrera y la verdad es que ya no se qué mas puedo hacer, estoy mirando por muchos sitios y no consigo encontrar respuesta. A lo mejor es una cosa facil de solucionar, pero no lo consigo.
Pongo mi correo por si alguien sabe como puedo hacerlo y me puede echar una mano: sitosc@hotmail.com
Gracias de antemano.