Introducción
Cuando queremos copiar un objeto en Java, hay dos posibilidades que debemos considerar: una copia superficial y una copia profunda.
la copia superficial es el enfoque cuando solo copiamos valores de campo y, por lo tanto, la copia puede depender del objeto original. En el enfoque de copia profunda, nos aseguramos de que todos los objetos del árbol se copien profundamente, de modo que la copia no dependa de ningún objeto existente anterior que pueda cambiar.,
en este artículo, compararemos estos dos enfoques y aprenderemos cuatro métodos para implementar la copia profunda.
más información:
Java Copy Constructor
Copiar Conjuntos en Java
copiando un HashMap en Java
Maven Setup
usaremos tres dependencias Maven: Gson, Jackson y Apache commons lang — para probar diferentes formas de realizar una copia profunda.
vamos a añadir estas dependencias a nuestro pom.xml:
las últimas versiones de Gson, Jackson y Apache Commons Lang se pueden encontrar en Maven Central.,
Model
para comparar diferentes métodos para copiar objetos Java, necesitaremos dos clases en las que trabajar:
class Address { private String street; private String city; private String country; // standard constructors, getters and setters}
class User { private String firstName; private String lastName; private Address address; // standard constructors, getters and setters}
Shallow Copy
una copia shallow es una copia en la que solo podemos copiar valores de campos de un objeto a otro:
en este caso pm != shallowCopy, lo que significa que son objetos diferentes, pero el problema es que cuando cambiamos cualquiera de las propiedades de la dirección original, esto también afectará la dirección de la shallowCopy.,
Nosotros no se preocupe si la Dirección era inmutable, pero no lo es:
Copia Profunda
Una copia profunda es una alternativa que soluciona este problema. Su ventaja es que al menos cada objeto mutable en el gráfico de objetos se copia recursivamente.
dado que la copia no depende de ningún objeto mutable que se haya creado anteriormente, no se modificará por accidente como vimos con la copia superficial.
en las siguientes secciones, mostraremos varias implementaciones de copia profunda y demostraremos esta ventaja.
5.1., Copy Constructor
la primera implementación que implementaremos se basa en constructores de copia:
public Address(Address that) { this(that.getStreet(), that.getCity(), that.getCountry());}
public User(User that) { this(that.getFirstName(), that.getLastName(), new Address(that.getAddress()));}
en la implementación anterior de la copia profunda, no hemos creado nuevas cadenas en nuestro constructor de copia porque String es una clase inmutable.
como resultado, no se pueden modificar por accidente. Veamos si esto funciona:
5.2. Interfaz Cloneable
la segunda implementación se basa en el método de clonación heredado de Object. Está protegido, pero tenemos que anularlo como público.,
también agregaremos una interfaz de marcador, Cloneable, a las clases para indicar que las clases son realmente cloneables.
agreguemos el método clone() A la clase Address:
Y ahora implementemos clone() para la clase User:
tenga en cuenta que el super.la llamada clone () devuelve una copia superficial de un objeto, pero establecemos copias profundas de campos mutables manualmente, por lo que el resultado es correcto:
bibliotecas externas
los ejemplos anteriores parecen fáciles, pero a veces no se aplican como una solución cuando no podemos agregar un constructor adicional o anular el método clone.,
esto puede suceder cuando no poseemos el código, o cuando el gráfico de objetos es tan complicado que no terminaríamos nuestro proyecto a tiempo si nos centráramos en escribir Constructores adicionales o implementar el método clone en todas las clases del gráfico de objetos.
entonces, ¿Qué? En este caso, podemos utilizar una biblioteca externa. Para lograr una copia profunda, podemos serializar un objeto y luego deserializarlo a un nuevo objeto.
veamos algunos ejemplos.
6.1., Apache Commons Lang
Apache Commons Lang tiene SerializationUtils#clone, que realiza una copia profunda cuando todas las clases en el gráfico de objetos implementan la interfaz Serializable.
si el método encuentra una clase que no es serializable, fallará y lanzará una excepción SerializationException sin marcar.
debido a eso, necesitamos agregar la interfaz Serializable a nuestras clases:
6.2. Serialización JSON con Gson
la otra forma de serializar es usar la serialización JSON. Gson es una biblioteca que se utiliza para convertir objetos en JSON y viceversa.,
a diferencia de Apache Commons Lang, GSON no necesita la interfaz Serializable para realizar las conversiones.
echemos un vistazo rápido a un ejemplo:
6.3. Serialización JSON Con Jackson
Jackson es otra biblioteca que admite la serialización JSON. Esta implementación será muy similar a la que usa Gson, pero necesitamos agregar el constructor predeterminado a nuestras clases.
veamos un ejemplo:
conclusión
¿Qué implementación debemos usar al hacer una copia profunda?, La decisión final a menudo dependerá de las clases que vamos a copiar y si poseemos las clases en el gráfico de objetos.
como siempre, los ejemplos de código completos para este tutorial se pueden encontrar en GitHub.
empezar con el Muelle 5 y el Resorte de Arranque 2, a través del aprendizaje del curso Primavera:
>> COMPRUEBE EL CURSO