otro - ¿Por qué la Interfaz Clonable de Java no es genérica?
java cloneable example (5)
Como una pregunta de pensamiento semi-relacionada: ¿sería útil crear una interfaz (llámala ReallyCloneable) que expone a clone () como miembro público?
Mi opinión es que no, no sería así. La capacidad de clonación está íntimamente ligada a la implementación de una clase concreta. No puedo pensar en un caso de uso único en el que pueda decir "Tengo un objeto arbitrario y quiero una copia". La pregunta inmediata sería: ¿por qué quieres esa copia?
Y la respuesta típica es para que pueda modificar la copia sin afectar el original. Sin embargo, para hacer eso necesitas saber qué tipo de objeto tienes, de lo contrario, ¿cómo sabrías a qué llamar para modificarlo? Y si lo sabe, sabrá si proporciona o no un método público de clonación ().
Pero, ¿qué ocurre si está programando en una interfaz, como List? La clonación inteligente (recursiva) (frente a la copia de datos de objeto a nivel de byte) sería increíblemente útil con el marco de Colecciones. Pero puede haber colecciones (como aquellas respaldadas por una base de datos) que no pueden soportar tal operación, por lo que no puede requerir que List exhiba un clon público (). Lo que lo lleva a crear instancias de su propia implementación concreta de listas para copiar los contenidos de la lista fuente.
Java 5 introdujo los genéricos y se agregaron a muchas interfaces en el paquete java.lang
. Sin embargo, Cloneable
no obtuvo genéricos. ¿Me pregunto porque?
Editar: En respuesta a las respuestas de @Jon y @litb, y el comentario de @Earwicker, pensé que Cloneable
podría ser:
public interface Cloneable<T> {
public T clone();
}
Aquí T clone();
anula Object.clone()
, dándole un tipo covariante. Creo que esto aún sería compatible con versiones anteriores y aumentaría la seguridad del tipo. ¿Entonces por qué no?
Edición 2: Como se puede ver en las respuestas (y comentarios) a continuación, la interfaz sugerida anteriormente rompería la compatibilidad hacia atrás. Como Object.clone()
está protected
, volver a escribirlo en la interfaz obligaría a todos los implementadores a proporcionar una implementación public
, lo que los diseñadores de clase podrían no desear (es decir, podrían optar por mantenerlo protected
).
La interfaz clonable no contiene ningún miembro. ¿Cuál sería el sentido de hacerlo genérico?
(Si Cloneable contuviera el método clone (), tendría sentido, pero eso está declarado en java.lang.Object).
EDIT: clone () está en java.lang.Object ya que tiene una implementación (que hace una copia de campo por campo). Un mejor diseño sería tener algo como .NET''s MemberwiseClone () como un método protegido en Object, y luego un método público de clonación () dentro de la interfaz. No sé por qué ese diseño no fue elegido.
(En .NET, ICloneable no es genérico porque existía antes de los genéricos; la naturaleza diferente de los genéricos .NET impide que un tipo previamente no genérico se vuelva genérico).
Sin embargo, tanto en Java como en .NET, la API de clonación "normal" generalmente se considera como algo malo, ya que no dice nada acerca de qué profundidad de clonación se debe realizar.
java.lang.Cloneable
es una interfaz de marcador. Es únicamente para este fin, de forma que Object.clone pueda lanzar una excepción para indicar que una clase no admite la clonación utilizando, por ejemplo,
if(!(this instanceof Cloneable))
throw...;
De la documentación:
El método clon para el objeto de clase realiza una operación de clonación específica. Primero, si la clase de este objeto no implementa la interfaz Cloneable, se lanza una CloneNotSupportedException.
No tiene métodos. Convertirlo en un genérico no va a ser de ninguna utilidad.
Esto no aborda su pregunta teórica, pero maneja el caso práctico:
Java 5 tiene valores de retorno covariantes y permite ampliar el acceso a los métodos. Por lo tanto, puede cambiar la firma de clone () apropiadamente en subclases.
public class MyClass implements Cloneable {
public MyClass clone(){ /* do the right stuff */ }
}
MyClass.clone()
es una anulación correcta de Object.clone()
, y no tendrá que escribir moldes.
Si Cloneable
era Cloneable<T>
, entonces sería imposible hacer esto:
class A extends B implements Cloneable<A>
class B implements Cloneable<B>
No puede implementar la misma interfaz con diferentes parámetros, y la clase A
implementa Cloneable<A>
y Cloneable<B>
.