2012-07-18 8 views
18

Considere lo siguiente del artículo 11 de Java efectivo (anule el clon juiciosamente) donde Josh Bloch explica qué está mal con el contrato clone().Java efectivo: Análisis del método clone()

Hay un número de problemas con este contrato. La disposición que "no se llaman constructores " es demasiado fuerte. Un método de clonación con buen comportamiento puede llamar a los constructores para crear objetos internos al clon en construcción. Si la clase es final, el clon puede incluso devolver un objeto creado por un constructor.

Puede alguien explicar lo que Josh Bloch está diciendo en el primer párrafo por "Si la clase es final, clone incluso puede devolver un objeto creado por un constructor." ¿Qué tiene que ver final con clone() aquí?

Respuesta

19

Si una clase no es definitiva, clone tiene que devolver la clase más derivada para la que se llamó. Eso no puede funcionar con un constructor, porque clone no sabe a cuál llamar. Si una clase es definitiva, no puede tener ninguna subclase, por lo que no hay peligro al llamar a su constructor cuando se clona.

2

El contrato para clone especifica que "Por convención, el objeto devuelto se debe obtener llamando al super.clone". Si su clase no es definitiva y devuelve algo obtenido con una invocación de constructor, llamar al super.clone() desde una subclase no devolverá el resultado esperado (en primer lugar, el tipo del objeto devuelto no será el tipo de la subclase, ya que el clone() método volvería).

6

Una clase no tiene que proporcionar su propia implementación de clone para poder clonarse. Puede delegar eso en su superclase clonable. Aquí viene el truco: clone siempre debe devolver una instancia de la misma clase que la instancia a la que se llama. Eso es imposible de lograr en el caso descrito si se llama a un constructor explícito. Si la clase que reemplaza clone es definitiva, por otro lado, estaría bien.

21

Se debe a que las implementaciones típicas de clone() se ven así:

public class MyClass implements Cloneable { 
    protected Object clone() { 
    MyClass cloned = (MyClass) super.clone(); 
    // set additional clone properties here 
    } 
} 

De esta manera se puede heredar el comportamiento de la clonación de su superclase. Es ampliamente se supone que el resultado de una operación clone() devolverá el tipo de instancia correcto en función del objeto al que se invocó. Es decir. this.getClass()

Así que si una clase es definitiva, no tiene que preocuparse por una subclase que llame a super.clone() y no recupere el tipo de objeto correcto.

public class A implements Cloneable { 
    public Object clone() { 
     return new A(); 
    } 
} 


public class B extends A { 
    public Object clone() { 
     B b = (B)super.clone(); // <== will throw ClassCastException 
    } 
} 

Pero, si A es definitiva, nadie puede extenderlo, y por lo tanto es seguro de usar un constructor.