Solución:
Josh Bloch proporciona algunos argumentos bastante buenos (incluido el que proporcionó) afirmando que Cloneable
es fundamentalmente defectuoso, favoreciendo a un constructor de copias en su lugar. Mira aquí.
Todavía no he encontrado un caso de uso práctico para copiar un objeto inmutable. Está copiando objetos por una razón específica, presumiblemente para aislar algún conjunto de objetos mutables en una sola transacción para su procesamiento, garantizando que nada pueda alterarlos hasta que esa unidad de procesamiento esté completa. Si ya son inmutables, una referencia es tan buena como una copia.
BeanUtils.copyProperties
es a menudo una forma menos intrusiva de copiar sin tener que alterar sus clases para ser compatible, y ofrece una flexibilidad única en la composición de objetos.
Dicho eso copyProperties
no siempre es de talla única. Es posible que en algún momento necesite admitir objetos que contengan tipos que tengan constructores especializados, pero que aún sean mutables. Sus objetos pueden admitir métodos internos o constructores para evitar esas excepciones, o puede registrar tipos específicos en alguna herramienta externa para copiar, pero no puede llegar a algunos lugares que incluso clone()
pueden. Es bueno, pero aún tiene límites.
BeanUtils es más flexible que el clon estándar que simplemente copia valores de campo de un objeto a otro. El método de clonación copia los campos de beans de la misma clase, pero BeanUtils puede hacerlo para 2 instancias de clases diferentes que tengan los mismos nombres de atributo.
Por ejemplo, supongamos que tiene un Bean A que tiene un campo String date y un Bean B que tienen el mismo campo java.util.Date date. con BeanUtils puede copiar el valor de la cadena y convertirlo automáticamente a la fecha usando DateFormat.
Lo he usado para convertir un objeto SOAP en objetos Hibernate que no tienen los mismos tipos de datos.
Revisé el código fuente y descubrí que solo está copiando el “primer nivel” de primitivo propiedades. Cuando se trata de un objeto anidado, las propiedades anidadas todavía hacen referencia a los campos del objeto original, por lo que no es una “copia profunda”.
Verifique estos fragmentos del código fuente de Spring de org.springframework.beans.BeanUtils.java
, versión 5.1.3:
/**
* Copy the property values of the given source bean into the target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* <p>This is just a convenience method. For more complex transfer needs,
* consider using a full BeanWrapper.
* @param source the source bean
* @param target the target bean
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
...
/**
* Copy the property values of the given source bean into the given target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
Solo concéntrate en esta línea:
writeMethod.invoke(target, value);
Esta línea llama al colocador en el objeto de destino. Imagina esta clase:
class Student {
private String name;
private Address address;
}
Si tenemos student1
y student2
, el segundo está intacto y no tiene asignado ningún campo, student1
tiene address1
y nombre John
.
Entonces, si llamamos:
BeanUtils.copyProperties(student1, student2);
Nosotros estamos haciendo:
student2.setName(student1.getName()); // this is copy because String is immutable
student2.setAddress(student1.getAddress()); // this is NOT copy, we still are referencing `address1`
Cuando address1
cambia, cambia student2
también.
Entonces, BeanUtils.copyProperties()
solo funciona en campos de tipos primitivos del objeto; si está anidado, no funciona; o, debe asegurarse de la inmutabilidad del objeto original durante todo el ciclo de vida del objeto de destino, lo cual no es fácil y deseable.
Si realmente desea convertirlo en una copia profunda, debe implementar alguna forma de recursivamente llama a este método en campos que no son primitivos. Por fin, llegará a una clase con solo campos primitivos / inmutables y luego habrá terminado.