example - new icon java
¿Cómo anular correctamente el método de clonación? (9)
¿Es absolutamente necesario usar clone
? La mayoría de las personas está de acuerdo en que el clone
de Java está roto.
Josh Bloch en diseño - Copy Constructor versus Cloning
Si has leído el artículo sobre la clonación en mi libro, especialmente si lees entre líneas, sabrás que creo que el
clone
está profundamente roto. [...] Es una pena queCloneable
esté roto, pero sucede.
Puede leer más discusión sobre el tema en su libro Effective Java 2nd Edition, Item 11: Anular el clone
juiciosamente . En su lugar, recomienda utilizar un constructor de copias o una copia de fábrica.
Continuó escribiendo páginas de páginas sobre cómo, si cree que debe hacerlo, debe implementar el clone
. Pero él cerró con esto:
¿Son todas estas complejidades realmente necesarias? Raramente. Si amplía una clase que implementa
Cloneable
, no tiene más remedio que implementar un método declone
buen comportamiento. De lo contrario, es mejor proporcionar medios alternativos para copiar objetos, o simplemente no proporcionar la capacidad .
El énfasis era suyo, no mío.
Como dejó en claro que no tiene otra opción que implementar el clone
, esto es lo que puede hacer en este caso: asegúrese de que MyObject extends java.lang.Object implements java.lang.Cloneable
. Si ese es el caso, entonces puedes garantizar que NUNCA CloneNotSupportedException
una CloneNotSupportedException
. Lanzar AssertionError
como algunos han sugerido parece razonable, pero también puede agregar un comentario que explique por qué nunca se ingresará el bloque catch en este caso particular .
Alternativamente, como otros también han sugerido, quizás puedas implementar el clone
sin llamar a super.clone
.
Necesito implementar un clon profundo en uno de mis objetos que no tiene superclase.
¿Cuál es la mejor forma de manejar la CloneNotSupportedException
marcada lanzada por la superclase (que es Object
)?
Un compañero de trabajo me aconsejó que lo manejara de la siguiente manera:
@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}
// Deep clone member fields here
return foo;
}
Esta parece ser una buena solución para mí, pero quería compartirla con la comunidad de StackOverflow para ver si hay otras ideas que pueda incluir. ¡Gracias!
A veces es más simple implementar un constructor de copia:
public MyObject (MyObject toClone) {
}
Le ahorra la molestia de manejar CloneNotSupportedException
, trabaja con los campos final
y no tiene que preocuparse por el tipo que devolverá.
El hecho de que la implementación de Cloneable de Java se haya roto no significa que no pueda crear uno propio.
Si el propósito real de OP es crear un clon profundo, creo que es posible crear una interfaz como esta:
public interface Cloneable<T> {
public T getClone();
}
luego use el constructor del prototipo mencionado anteriormente para implementarlo:
public class AClass implements Cloneable<AClass> {
private int value;
public AClass(int value) {
this.vaue = value;
}
protected AClass(AClass p) {
this(p.getValue());
}
public int getValue() {
return value;
}
public AClass getClone() {
return new AClass(this);
}
}
y otra clase con un campo de objeto AClass:
public class BClass implements Cloneable<BClass> {
private int value;
private AClass a;
public BClass(int value, AClass a) {
this.value = value;
this.a = a;
}
protected BClass(BClass p) {
this(p.getValue(), p.getA().getClone());
}
public int getValue() {
return value;
}
public AClass getA() {
return a;
}
public BClass getClone() {
return new BClass(this);
}
}
De esta forma, puedes clonar fácilmente un objeto de clase BClass sin necesidad de @SuppressWarnings u otro código truco.
Hay dos casos en los que se CloneNotSupportedException
la CloneNotSupportedException
:
- La clase que se está
Cloneable
no implementaCloneable
(suponiendo que la clonación real eventualmente se revierte al método de clonación deObject
). Si la clase en la que está escribiendo este método implementaCloneable
, esto nunca sucederá (ya que cualquier subclase la heredará adecuadamente). - La excepción es lanzada explícitamente por una implementación: esta es la forma recomendada de evitar la clonabilidad en una subclase cuando la superclase es
Cloneable
.
Este último caso no puede ocurrir en su clase (ya que está llamando directamente al método de la superclase en el bloque try
, incluso si se invoca desde una subclase que llama a super.clone()
) y el primero no debería hacerlo, ya que su clase debería implementar Cloneable
.
Básicamente, debe registrar el error con seguridad, pero en esta instancia en particular solo ocurrirá si arruina la definición de su clase. Por lo tanto, trátelo como una versión comprobada de NullPointerException
(o similar): nunca se lanzará si su código es funcional.
En otras situaciones, necesitaría estar preparado para esta eventualidad: no hay garantía de que un objeto dado sea clonable, por lo que al detectar la excepción debe tomar las medidas apropiadas según esta condición (continuar con el objeto existente, tomar una estrategia de clonación alternativa). ej. serialize-deserialize, lanza una IllegalParameterException
si tu método requiere el parámetro por clonación, etc., etc.).
Editar : Aunque en general debo señalar que sí, clone()
es realmente difícil de implementar correctamente y difícil para las personas que llaman saber si el valor de retorno será el que desean, doblemente cuando se consideran clones profundos versus superficiales. A menudo es mejor evitar todo por completo y usar otro mecanismo.
La forma en que funciona su código es bastante similar a la forma "canónica" de escribirlo. Aunque arrojaría un AssertionError
dentro de la captura. Señala que esa línea nunca debería ser alcanzada.
catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
Por mucho que la mayoría de las respuestas aquí sean válidas, debo decir que su solución también es cómo lo hacen los desarrolladores reales de la API de Java. (Ya sea Josh Bloch o Neal Gafter)
Aquí hay un extracto de openJDK, clase ArrayList:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn''t happen, since we are Cloneable
throw new InternalError(e);
}
}
Como habrás notado y otros mencionaron, CloneNotSupportedException
casi no tiene posibilidad de ser lanzado si declaraste que implementabas la interfaz Cloneable
.
Además, no es necesario que anule el método si no hace nada nuevo en el método reemplazado. Solo necesita anularlo cuando necesite realizar operaciones adicionales en el objeto o debe hacerlo público.
En última instancia, es mejor evitarlo y hacerlo de otra manera.
Puede implementar constructores de copias protegidas de la siguiente manera:
/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
// etc
}
public MyObject clone() {
return new MyObject(this);
}
Use la serialization para hacer copias en profundidad. Esta no es la solución más rápida, pero no depende del tipo.
public class MyObject implements Cloneable, Serializable{
@Override
@SuppressWarnings(value = "unchecked")
protected MyObject clone(){
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bOs = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bOs);
oos.writeObject(this);
ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
return (MyObject)ois.readObject();
} catch (Exception e) {
//Some seriouse error :< //
return null;
}finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
}
if (ois != null)
try {
ois.close();
} catch (IOException e) {
}
}
}
}