Copiar objetos es una operación común en proyectos empresariales. Al copiar un objeto, debemos asegurarnos de terminar con una nueva instancia que contenga los valores que queremos.
Los objetos de dominio suelen ser complejos. Hacer una copia con el objeto raíz y los objetos compuestos tampoco es trivial.
Exploremos las formas más efectivas de copiar un objeto utilizando técnicas de copia superficial y profunda.
Referencias de objetos
Para realizar correctamente una copia superficial o profunda de un objeto, primero debemos saber qué no hacer. Comprender las referencias a objetos es esencial para utilizar técnicas de copia superficial y profunda.
Al realizar una copia de un objeto, es importante evitar utilizar la misma referencia de objeto. Es un error fácil, como muestra este ejemplo. Para empezar, aquí está el Product
objeto que usaremos en nuestros ejemplos:
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public double getPrice() { return price; }
public void setName(String name) { this.name = name; }
public void setPrice(double price) { this.price = price; }
}
Ahora, creemos y asignemos un Product
referencia de objeto a otra variable. Parece una copia, pero en realidad es el mismo objeto:
public static void main(String[] args) {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = product;
product.name = "Alienware";
System.out.println(product.name);
System.out.println(copyOfProduct.name);
}
La salida de este código es
Alienware
Alienware
Observe en el código anterior que asignamos el valor del objeto a una variable local diferente, pero esta variable apunta a la misma referencia de objeto. Si cambiamos el product
o copyOfProduct
objetos, el resultado será un cambio al original. Product
objeto.
Esto se debe a que cada vez que creamos un objeto en Java, se crea una referencia de objeto en el montón de memoria de Java. Esto nos permite modificar objetos usando sus variables de referencia.
Copia superficial
La técnica de copia superficial nos permite copiar valores de objetos simples a un objeto nuevo sin incluir los valores del objeto interno. Como ejemplo, aquí se explica cómo utilizar la técnica de copia superficial para copiar el Product
objeto sin utilizar su referencia de objeto:
// Omitted the Product object
public class ShallowCopyPassingValues {
public static void main(String[] args) {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = new Product(product.getName(), product.getPrice());
product.setName("Alienware");
System.out.println(product.getName());
System.out.println(copyOfProduct.getName());
}
}
La salida es
Alienware
Macbook Pro
Observe en este código que cuando pasamos los valores de un objeto a otro, se crean dos objetos diferentes en el montón de memoria. Cuando cambiamos uno de los valores en el nuevo objeto, los valores seguirán siendo los mismos en el objeto original. Esto prueba que los objetos son diferentes y que hemos ejecutado con éxito la copia superficial.
Nota: El Patrón de diseño de constructor es otra forma de realizar la misma acción.
Copia superficial con Cloneable
Desde Java 7, hemos tenido la Cloneable
interfaz en Java. Esta interfaz proporciona otra forma de copiar objetos. En lugar de implementar la lógica de copia manualmente, como acabamos de hacer, podemos implementar la Cloneable
interfaz y luego implementar la clone()
método. Usando Cloneable
y el clone()
El método da como resultado automáticamente una copia superficial.
No me gusta esta técnica porque genera una excepción marcada y tenemos que convertir manualmente un tipo de clase, lo que hace que el código sea detallado. Pero usando Cloneable
Podría simplificar el código si tenemos un objeto de dominio enorme con muchos atributos.
Esto es lo que sucede si implementamos el Cloneable
interfaz en un objeto de dominio y luego anular la clone()
método:
public class Product implements Cloneable {
// Omitted attributes, methods and constructor
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Ahora, aquí está el método de copia en acción nuevamente:
public class ShallowCopyWithCopyMethod {
public static void main(String[] args) throws CloneNotSupportedException {
Product product = new Product("Macbook Pro", 3000);
Product copyOfProduct = (Product) product.clone();
product.setName("Alienware");
System.out.println(product.getName());
System.out.println(copyOfProduct.getName());
}
}
Como puedes ver, el método de copia funciona perfectamente para hacer una copia superficial de un objeto. Usarlo significa que no necesitamos copiar todos los atributos manualmente.
copia profunda
La técnica de copia profunda es la capacidad de copiar los valores de un objeto compuesto a otro objeto nuevo. Si el Product
objeto contiene el Category
objeto, por ejemplo, se espera que todos los valores de ambos objetos se copien en un nuevo objeto.
¿Qué pasa si el Product
objeto tiene un objeto compuesto? ¿Funcionará la técnica de copia superficial? Veamos qué pasa si intentamos usar sólo el copy()
método.
Para empezar, componemos el Product
clase con el Order
objeto:
public class Product implements Cloneable {
// Omitted other attributes, constructor, getters and setters
private Category category;
public Category getCategory() { return category; }
}
Ahora, hagamos lo mismo usando el super.clone()
método:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
Category category = new Category("Laptop", "Portable computers");
Product product = new Product("Macbook Pro", 3000, category);
Product copyOfProduct = (Product) product.clone();
Category copiedCategory = copyOfProduct.getCategory();
System.out.println(copiedCategory.getName());
}
}
La salida es
Laptop
Tenga en cuenta que, aunque el resultado es «Laptop», la operación de copia profunda no se realizó. Lo que pasó en cambio es que tenemos lo mismo Category
referencia de objeto. Aquí está la prueba:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
// Same code as the example above
copiedCategory.setName("Phone");
System.out.println(copiedCategory.getName());
System.out.println(category.getName());
}
}
Producción:
Laptop
Phone
Phone
Observe en este código que no se hizo una copia cuando cambiamos el Category
objeto. En cambio, sólo había una asignación de objeto a una variable diferente. Por lo tanto, cambiaremos el objeto que creamos en el montón de memoria cada vez que cambiemos la variable de referencia.
Copia profunda con el método clone()
Ahora sabemos que el clone()
El método no funcionará para una copia profunda si tenemos una anulación simple. Veamos cómo podemos hacerlo funcionar.
Primero, implementamos Cloneable
en el Category
clase:
public class Category implements Cloneable {
// Omitted attributes, constructor, getters and setters
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Ahora tenemos que cambiar la implementación del Product
método de clonación también para clonar el Category
objeto:
public class ProductWithDeepCopy implements Cloneable {
// Omitted attributes, constructor, getters and setters
@Override
protected Object clone() throws CloneNotSupportedException {
this.category = (Category) category.clone();
return super.clone();
}
}
Si intentamos realizar la copia profunda con el mismo ejemplo de código anterior, obtendremos una copia real de los valores del objeto en un nuevo objeto, como se muestra aquí:
public class TryDeepCopyWithClone {
public static void main(String[] args) throws CloneNotSupportedException {
Category category = new Category("Laptop", "Portable computers");
Product product = new Product("Macbook Pro", 3000, category);
Product copyOfProduct = (Product) product.clone();
Category copiedCategory = copyOfProduct.getCategory();
System.out.println(copiedCategory.getName());
copiedCategory.setName("Phone");
System.out.println(copiedCategory.getName());
System.out.println(category.getName());
}
}
La salida es
Laptop
Phone
Laptop
Dado que copiamos manualmente el método de categoría en el copy()
método de Product
, finalmente funciona. Recibiremos una copia de Product
y Category
utilizando el copy()
método de Product
.
Este código demuestra que la copia profunda funcionó. Los valores de los objetos originales y copiados son diferentes. Por tanto, no es la misma instancia; es un objeto copiado.
Copia superficial con serialización.
A veces es necesario serializar un objeto para transformarlo en bytes y pasarlo por una red. Esta operación puede ser peligrosa porque si no se valida correctamente, el objeto serializado podría verse explotado. La seguridad de la serialización de Java está fuera del alcance de este artículo, pero veamos cómo funciona con el código.
Usaremos la misma clase del ejemplo anterior pero esta vez implementaremos el Serializable
interfaz:
public class Product implements Serializable, Cloneable {
// Omitted attributes, constructor, getters, setters and clone method
}
Observe que sólo el Product
se serializará ya que solo el Product
implementos Serializable
. El Category
El objeto no será serializado. He aquí un ejemplo:
public class ShallowCopySerializable {
public static void main(String[] args) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
Product product = new Product("Macbook Pro", 3000);
out.writeObject(product);
out.flush();
out.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
Product clonedProduct = (Product) in.readObject();
in.close();
System.out.println(clonedProduct.getName());
Category clonedCategory = clonedProduct.getCategory();
System.out.println(clonedCategory);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
La salida es
Macbook Pro
null
Ahora bien, si intentáramos poblar el Category
objeto en el Product
objeto, el java.io.NotSerializableException
sería arrojado. Eso es porque el Category
objeto no implementa Serializable
.
Copia profunda con serialización
Ahora, veamos qué sucede si usamos el mismo código que el anterior pero agregamos lo siguiente en el Category
clase:
public class Category implements Serializable, Cloneable {
// Omitted attributes, constructor, getters, setters and clone method
// Adding toString for a good Object description
@Override
public String toString() {
return "Category{" + "name="" + name + "\'' + ", description='" + description + '\'' + '}';
}
}
Al ejecutar el mismo código que la copia superficial serializable, obtendremos el resultado de Category
también, y el resultado debería ser el siguiente:
Macbook Pro
Category{name="Laptop", description='Portable computers'}
Nota: Para explorar más a fondo la serialización de Java, pruebe el desafío del código Java aquí.
Conclusión
A veces, la técnica de copia superficial es todo lo que necesitas para clonar un objeto superficialmente. Pero cuando quieras copiar tanto el objeto como sus objetos internos, debes implementar una copia profunda manualmente. Estas son las conclusiones clave de estas importantes técnicas.
Qué recordar sobre la copia superficial
- Una copia superficial crea un nuevo objeto pero comparte las referencias de los objetos internos con el objeto original.
- Los objetos copiados y originales se refieren a los mismos objetos en la memoria.
- Los cambios realizados en los objetos internos a través de una referencia se reflejarán tanto en los objetos copiados como en los originales.
- La copia superficial es un proceso simple y eficiente.
- Java proporciona una implementación predeterminada de copia superficial a través del
clone()
método.
Qué recordar sobre la copia profunda
- Una copia profunda crea un nuevo objeto y también crea nuevas copias de sus objetos internos.
- Los objetos copiados y originales tienen copias independientes de los objetos internos.
- Los cambios realizados en los objetos internos a través de una referencia no afectarán a la otra.
- La copia profunda es un proceso más complejo, especialmente cuando se trata de gráficos de objetos o referencias anidadas.
- La copia profunda debe implementarse explícitamente, ya sea manualmente o mediante bibliotecas o marcos.
Esta historia, «Cómo copiar objetos en Java: copia superficial y copia profunda» fue publicada originalmente por
Copyright © 2024 IDG Communications, Inc.