2011-09-28 12 views
16

No entiendo el mecanismo de clonación de objetos personalizados. Por ejemplo:Cómo clonar un objeto Java con el método clone()

public class Main{ 

    public static void main(String [] args) { 

     Person person = new Person(); 
     person.setFname("Bill"); 
     person.setLname("Hook"); 

     Person cloned = (Person)person.clone(); 
     System.out.println(cloned.getFname() + " " + cloned.getLname()); 
    } 
} 

class Person implements Cloneable{ 

    private String fname; 
    private String lname; 

    public Object clone() { 

     Person person = new Person(); 
     person.setFname(this.fname); 
     person.setLname(this.lname); 
     return person; 
    } 

    public void setFname(String fname) { 
     this.fname = fname; 
    } 

    public void setLname(String lname){ 
     this.lname = lname; 
    } 

    public String getFname(){ 
     return fname; 
    } 

    public String getLname() { 
     return lname; 
    } 
} 

Este es un ejemplo de la manera correcta de clonar como en la escritura de libros. Pero puedo eliminar implements Cloneable en la definición de nombre de clase y recibo el mismo resultado.

Así que no entiendo la propuesta de clonación y por qué el método clone() se define en la clase ¿Objeto?

+0

consulte el Javadoc: http://download.oracle.com/javase/6/docs/api/java/lang/Cloneable.html –

+0

http://stackoverflow.com/questions/2156120/java-recommended-solution-for-deep-cloning-copying-an-instance/2156367#2156367 – Bozho

+0

http: // stackoverflow.com/questions/3180599/javas-clone-method-generator-for-eclipse-galileo/3180729 # 3180729 – Bozho

Respuesta

8

El clone() en la clase Object hace una copia superficial de la memoria en lugar de llamar a métodos como el constructor. Para llamar al clone() en cualquier objeto que no implemente el clone(), debe implementar la interfaz Clonable.

Si reemplaza el método clone(), no tiene que implementar esa interfaz.

Así como dice el JavaDoc, esto daría lugar a una excepción:

class A { 
    private StringBuilder sb; //just some arbitrary member 
} 

... 

new A().clone(); //this will result in an exception, since A does neither implement Clonable nor override clone() 

Si A en el ejemplo implementaría Clonable llamando clone() (la versión Object) daría lugar a un nuevo A ejemplo que hace referencia a la muy mismo StringBuilder, es decir, los cambios a sb en la instancia clonada darían lugar a cambios en el sb en la instancia original A.

Eso se entiende con una copia superficial y esa es una razón por la cual generalmente es mejor anular clone().

Editar: así como una nota al margen, utilizando el tipo de devolución de covarianza haría su anulado clone() más explícito:

public Person clone() { 
    ... 
} 
+0

Thomas, si no implemento Cloneable en A y no anulo clone() recibo un error de compilación, porque clone() está protegido –

+0

@Dmytro sí, el ejemplo fue solo para ilustrar el punto. Podrías llamar a 'clone()' incluso si está protegido usando la reflexión, o podrías llamar a 'super.clone()' en tu versión anulada: en ambos casos obtendrías la excepción cuando faltara la interfaz 'Clonable'. – Thomas

0

Es el mismo propósito que dicha interfaz. Principalmente permite que los métodos (etc.) acepten CUALQUIER objeto Clonable y que tengan acceso al método que necesitan sin limitarse a un objeto específico. Hay que admitir que Clonable es probablemente una de las menos interfaces útiles a este respecto, pero ciertamente hay lugares en los que podría desearla. Si quieres más idea, considera la interfaz Comparable que, por ejemplo, te permite tener listas ordenadas (porque la lista no necesita saber cuál es el objeto, solo que se pueden comparar).

0

No es necesario crear el objeto explícitamente aquí en el método clone(). Simplemente llamando al super.clone() creará la copia de este objeto. Realizará el clon superficial.

8

La JVM puede clonar el objeto, por lo que no debe construir una nueva persona usted mismo. Sólo tiene que utilizar este código:

class Person implements Cloneable { 
    // ... 
    @Override 
    public Object clone() throws CloneNotSupportedException { 
     return super.clone(); 
    } 
} 

o

class Person implements Cloneable { 
    // ... 
    @Override 
    public Object clone() { 
     try { 
      return super.clone(); 
     } 
     catch (CloneNotSupportedException e) { 
      throw new Error("Something impossible just happened"); 
     } 
    } 
} 

Esto funciona incluso si la clase de persona es una subclase, mientras que su aplicación clon siempre va a crear una instancia de la persona (y no una instancia de Empleado para un empleado, por ejemplo).

0

si no declara la interfaz clonable, debería obtener CloneNotSupportException cuando llame al método de clonación. Si declara y llama al método de clonación, hará una copia superficial.

12

El método de clonación está destinado a hacer una copia profunda. Asegúrese de comprender la diferencia entre copias profundas y superficiales. En su caso, un constructor de copia puede ser el patrón que desee. En algunos casos, no puede usar este patrón, por ejemplo, porque está subclasificando la clase X y no tiene acceso al constructor de X que necesita. Si X anula su método clone correctamente (si es necesario), entonces podría hacer una copia de la siguiente manera:

class Y extends X implements Cloneable { 

    private SomeType field; // a field that needs copying in order to get a deep copy of a Y object 

    ... 

    @Override 
    public Y clone() { 
     final Y clone; 
     try { 
      clone = (Y) super.clone(); 
     } 
     catch (CloneNotSupportedException ex) { 
      throw new RuntimeException("superclass messed up", ex); 
     } 
     clone.field = this.field.clone(); 
     return clone; 
    } 

} 

En general, cuando anulando su método clone: ​​

  • Hacer el tipo de retorno más específica
  • de inicio con llamar super.clone()
  • no incluya cláusula throws cuando se sabe clone() también funcionará para cualquier subclase (debilidad del clon de patrón; hacer clase final, si es posible)
  • dejar campos inmutables y primitivas solo, pero clon campos de objetos mutables manualmente después de la llamada a super.clone() (otra debilidad del clon de patrón, puesto que estos campos no se pueden hacer definitiva)

El método clone() de Object (que eventualmente se llamará cuando todas las superclases obedezcan el contrato) hace una copia superficial y se ocupa del tipo de tiempo de ejecución correcto del nuevo objeto. Tenga en cuenta que no se llama a ningún constructor en todo el proceso.

Si desea poder llamar al clone() en instancias, implemente la interfaz Cloneable y haga público el método. Si no desea poder invocar instancias, pero sí desea asegurarse de que las subclases puedan llamar a su super.clone() y obtener lo que necesitan, entonces no implemente Cloneable y mantenga el método protected si su superclase no tiene lo declaró público ya.

El patrón de clonación es difícil y tiene muchos inconvenientes. Asegúrate de que sea lo que necesitas. Considere los constructores de copia, o un método de fábrica estático.

0

En su ejemplo, no está haciendo una clonación real. Se anula el método clone() de la clase de objeto y se le asigna su propia implementación. pero en su método de clonación está creando un nuevo objeto Person. y devolverlo. Así que en este caso el objeto real no se clona.

Así que su método clone debe ser como:

public Object clone() { 
     return super.clone(); 
    } 

Así que aquí clon será manejado por el método de la superclase.