Acerca de Java clonable

95

Estaba buscando algunos tutoriales que explicaran sobre Java Cloneable, pero no obtuve ningún buen enlace, y Stack Overflow se está volviendo una opción más obvia de todos modos.

Me gustaría saber lo siguiente:

  1. Cloneablesignifica que podemos tener un clon o una copia de objetos, implementando la Cloneableinterfaz. ¿Cuáles son las ventajas y desventajas de hacer eso?
  2. ¿Cómo ocurre la clonación recursiva si el objeto es un objeto compuesto?
soñador
fuente
2
¿Las ventajas y desventajas en comparación con qué?
Galactus
4
Leí que significa que la ventaja de que una clase sea clonable frente a no. No estoy seguro de qué otra cosa podría interpretarse: S
allyourcode

Respuestas:

159

Lo primero que debe saber Cloneablees: no lo use.

Es muy difícil implementar la clonación con Cloneablederechos y el esfuerzo no vale la pena.

En lugar de eso, use algunas otras opciones, como apache-commons SerializationUtils(clon profundo) o BeanUtils(clon superficial), o simplemente use un constructor de copia.

Consulte aquí las opiniones de Josh Bloch sobre la clonación con Cloneable, lo que explica los muchos inconvenientes del enfoque. ( Joshua Bloch era un empleado de Sun y dirigió el desarrollo de numerosas funciones de Java).

Bozho
fuente
1
Vinculé
3
Tenga en cuenta que Block dice que no use Cloneable. No dice que no uses la clonación (o al menos espero que no). Hay muchas formas de implementar la clonación simplemente que son mucho más eficientes que las clases como SerializationUtils o BeanUtils que usan la reflexión. Vea mi publicación a continuación para ver un ejemplo.
Charles
¿Cuál es la alternativa a un constructor de copias al definir interfaces? simplemente agregue un método de copia?
benez
@benez yo diría que sí. desde java-8 puede tener staticmétodos en las interfaces, así que solo proporcione un static WhatEverTheInterface copy(WhatEverTheInterface initial)? pero me pregunto qué le da esto, ya que copia campos de un objeto mientras clona, ​​pero una interfaz define solo métodos. ¿le importaria explicar?
Eugene
40

Lamentablemente, Cloneable es solo una interfaz de marcador, es decir: no define el método clone ().

Lo que sí hace es cambiar el comportamiento del método protegido Object.clone (), que lanzará una CloneNotSupportedException para las clases que no implementan Cloneable, y realizará una copia superficial de miembros para las clases que sí lo hagan.

Incluso si este es el comportamiento que está buscando, necesitará implementar su propio método clone () para hacerlo público.

Al implementar su propio clone (), la idea es comenzar con el objeto creado por super.clone (), que está garantizado para ser de la clase correcta, y luego hacer cualquier población adicional de campos en caso de que una copia superficial no sea lo que usted quiere. Llamar a un constructor desde clone () sería problemático ya que esto rompería la herencia en caso de que una subclase quiera agregar su propia lógica clonable adicional; si llamara a super.clone () obtendría un objeto de la clase incorrecta en este caso.

Sin embargo, este enfoque evita cualquier lógica que pueda definirse en sus constructores, lo que podría ser potencialmente problemático.

Otro problema es que las subclases que se olvidan de anular clone () heredarán automáticamente la copia superficial predeterminada, que probablemente no sea lo que desea en caso de estado mutable (que ahora se compartirá entre la fuente y la copia).

La mayoría de los desarrolladores no usan Cloneable por estas razones y simplemente implementan un constructor de copia en su lugar.

Para obtener más información y las posibles dificultades de Cloneable, recomiendo encarecidamente el libro Effective Java de Joshua Bloch.

Luke Hutteman
fuente
12
  1. La clonación invoca una forma extralingüística de construir objetos, sin constructores.
  2. La clonación requiere que trate de alguna manera con CloneNotSupportedException, o que moleste el código del cliente para tratarlo.
  3. Los beneficios son pequeños, simplemente no tiene que escribir manualmente un constructor de copia.

Por lo tanto, use Cloneable con prudencia. No le brinda suficientes beneficios en comparación con el esfuerzo que necesita para aplicar para hacer todo bien.

Vladimir Ivanov
fuente
Como dijo Bozho, no use Cloneable. En su lugar, utilice un constructor de copias. javapractices.com/topic/TopicAction.do?Id=12
Bane
@Bane, ¿qué pasa si no conoce el tipo de objeto a clonar, cómo sabría qué constructor de copia de clase invocar?
Steve Kuo
@Steve: No lo sigo. Si va a clonar un objeto, supongo que ya sabe de qué tipo es; después de todo, tiene el objeto en la mano que planea clonar. Y si hay una situación en la que su objeto ha perdido su tipo específico a uno más genérico, ¿no podría evaluarlo usando una simple 'instancia de'?
Bane
4
@Bane: Suponga que tiene una lista de objetos derivados del tipo A, tal vez con 10 tipos diferentes. No sabes cuál es el tipo de cada objeto. Usar instanceof en este caso es MUY mala idea. Si agrega otro tipo, cada vez que haga esto, tendrá que agregar otra instancia de prueba. Y, ¿qué pasa si las clases derivadas están en otro paquete al que ni siquiera puede acceder? La clonación es un patrón común. Sí, la implementación de Java es mala, pero hay muchas formas de evitarla que funcionarán bien. Un constructor de copias no es una operación equivalente.
Charles
@Charles: En ausencia de un ejemplo detallado, y sin experiencia reciente para lidiar con este tipo de problema, tendré que remitirme a Bloch. Ítem ​​# 11. Es largo y un poco difícil de leer, pero básicamente dice "evita los clonables siempre que puedas, los constructores de copias son tus amigos".
Bane
7

La clonación es un paradigma de programación básico. El hecho de que Java pueda haberlo implementado de manera deficiente de muchas maneras no disminuye en absoluto la necesidad de clonación. Y, es fácil implementar la clonación que funcionará como usted quiera que funcione, superficial, profunda, mixta, lo que sea. Incluso puede usar el nombre clon para la función y no implementar Cloneable si lo desea.

Supongamos que tengo las clases A, B y C, donde B y C se derivan de A. Si tengo una lista de objetos de tipo A como esta:

ArrayList<A> list1;

Ahora, esa lista puede contener objetos de tipo A, B o C. No sabe de qué tipo son los objetos. Entonces, no puede copiar la lista de esta manera:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Si el objeto es en realidad de tipo B o C, no obtendrá la copia correcta. ¿Y si A es abstracto? Ahora, algunas personas han sugerido esto:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Esta es una muy, muy mala idea. ¿Qué pasa si agrega un nuevo tipo derivado? ¿Qué pasa si B o C están en otro paquete y no tiene acceso a ellos en esta clase?

Lo que le gustaría hacer es esto:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Mucha gente ha indicado por qué la implementación básica de Java del clon es problemática. Pero, se supera fácilmente de esta manera:

En clase A:

public A clone() {
    return new A(this);
}

En clase B:

@Override
public B clone() {
    return new B(this);
}

En clase C:

@Override
public C clone() {
    return new C(this):
}

No estoy implementando Cloneable, solo uso el mismo nombre de función. Si no le gusta, asígnele otro nombre.

Charles
fuente
Acabo de ver esto después de responder a su comentario en una respuesta separada; Veo a dónde va ahora, sin embargo, 2 cosas: 1) el OP preguntó específicamente sobre el uso de Cloneable (no sobre el concepto genérico de clonación), y 2) está dividiendo un poco los pelos aquí al tratar de distinguir entre un constructor de copias y el concepto genérico de clonación. La idea que transmite aquí es válida, pero en su raíz solo está usando un constructor de copia. ;)
Bane
Aunque quiero decir que estoy de acuerdo con su enfoque aquí, el de incluir un A # copyMethod (), en lugar de obligar al usuario a llamar directamente al constructor de copias.
Bane
5

R) No hay muchas ventajas de clonar sobre un constructor de copias. Probablemente, el más grande es la capacidad de crear un nuevo objeto del mismo tipo dinámico exacto (asumiendo que el tipo declarado es clonable y tiene un método de clonación público).

B) El clon predeterminado crea una copia superficial, y seguirá siendo una copia superficial a menos que la implementación de su clon cambie eso. Esto puede ser difícil, especialmente si tu clase tiene campos finales.

Bozho tiene razón, clonar puede ser difícil de hacer bien. Un constructor / fábrica de copias satisfará la mayoría de las necesidades.

ILMTitan
fuente
0

¿Cuáles son las desventajas de Cloneable?

La clonación es muy peligrosa si el objeto que está copiando tiene composición. Debe pensar en los posibles efectos secundarios a continuación en este caso porque la clonación crea una copia superficial:

Digamos que tiene un objeto para manejar la manipulación relacionada con la base de datos. Digamos, ese objeto tiene Connectionobjeto como una de las propiedades.

Así que cuando alguien crea clon originalObject, el objeto que está siendo creado, vamos a decir, cloneObject. Aquí el originalObjecty cloneObjecttiene la misma referencia para el Connectionobjeto.

Digamos que originalObjectcierra el Connectionobjeto, por lo que ahora cloneObjectno funcionará porque el connectionobjeto fue compartido entre ellos y en realidad fue cerrado por originalObject.

Puede ocurrir un problema similar si digamos que desea clonar un objeto que tiene IOStream como propiedad.

¿Cómo ocurre la clonación recursiva si el objeto es un objeto compuesto?

Cloneable realiza copia superficial. El significado es que los datos del objeto original y el objeto clonado apuntarán a la misma referencia / memoria. al contrario, en el caso de la copia profunda, los datos de la memoria del objeto original se copian en la memoria del objeto clonado.

027
fuente
Tu último párrafo es muy confuso. Cloneableno realiza una copia, lo Object.clonehace. "Los datos de la memoria del objeto original se copian en la memoria del objeto clonado" es precisamente lo que Object.clonehace. Necesita hablar sobre la memoria de los objetos referenciados para describir la copia profunda.
aioobe