Par son apparence - BeanUtils.copyProperties
semble créer un clone d'un objet. Si tel est le cas, et qu'en est-il des problèmes liés à la mise en œuvre de l'interface Cloneable (seuls les objets immuables sont nouveaux alors que les objets mutables ont des références copiées), quel est le meilleur et pourquoi?
Hier, j'ai implémenté cloneable et j'ai réalisé que je devais fournir mes propres modifications pour les éléments non String/Primative. J'ai ensuite été informé de BeanUtils.copyProperties
que j'utilise maintenant. Les deux implémentations semblent fournir une fonctionnalité similaire.
Merci
Josh Bloch fournit quelques arguments assez bons (y compris celui que vous avez fourni) affirmant que Cloneable
est fondamentalement défectueux, favorisant plutôt un constructeur de copie. Voir ici .
Je n'ai pas encore rencontré de cas d'utilisation pratique pour copier un objet immuable. Vous copiez des objets pour une raison spécifique, probablement pour isoler un ensemble d'objets mutables dans une seule transaction pour le traitement, garantissant que rien ne peut les modifier jusqu'à ce que cette unité de traitement soit terminée. S'ils sont déjà immuables, une référence est aussi bonne qu'une copie.
BeanUtils.copyProperties
Est souvent un moyen de copie moins intrusif sans avoir à modifier vos classes pour être pris en charge, et il offre une flexibilité unique dans la composition d'objets.
Cela dit, copyProperties
n'est pas toujours à taille unique. Vous devrez peut-être à un moment donné prendre en charge des objets contenant des types qui ont des constructeurs spécialisés, mais qui sont toujours modifiables. Vos objets peuvent prendre en charge des méthodes ou des constructeurs internes pour contourner ces exceptions, ou vous pouvez enregistrer des types spécifiques dans un outil externe pour la copie, mais il ne peut pas atteindre certains endroits que même clone()
peut. C'est bien, mais il y a quand même des limites.
BeanUtils est plus flexible qu'un clone standard qui copie simplement les valeurs de champ d'un objet à un autre. La méthode clone copie les champs à partir des beans de la même classe, mais BeanUtils peut le faire pour 2 instances de classes différentes ayant les mêmes noms d'attribut.
Par exemple, supposons que vous avez un Bean A qui a une date de chaîne de champ et un Bean B qui ont la même date de date Java.util.Date. avec BeanUtils, vous pouvez copier la valeur de la chaîne et la convertir automatiquement à ce jour à l'aide de DateFormat.
Je l'ai utilisé pour convertir un objet SOAP en objets Hibernate qui n'ont pas les mêmes types de données.
Je pense que vous cherchez une copie complète. vous pouvez avoir la méthode ci-dessous dans une classe util et l'utiliser pour tout type d'objet.
public static <T extends Serializable> T copy(T input) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(input);
oos.flush();
byte[] bytes = baos.toByteArray();
bis = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bis);
Object result = ois.readObject();
return (T) result;
} catch (IOException e) {
throw new IllegalArgumentException("Object can't be copied", e);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to reconstruct serialized object due to invalid class definition", e);
} finally {
closeQuietly(oos);
closeQuietly(baos);
closeQuietly(bis);
closeQuietly(ois);
}
}
D'après votre question, je suppose que vous avez besoin copie complète de l'objet. Si tel est le cas, n'utilisez pas la méthode clone
car elle est déjà spécifiée dans Oracle docs
qu'il fournit copie superficielle de l'objet associé. Et je n'ai pas assez d'idée sur BeanUtils.copyProperties
API.
Ci-dessous se trouve la courte démo de deep copy
. Ici, je copie profondément un primitive array
. Vous pouvez essayer ce code avec n'importe quel type d'objet.
import Java.io.*;
class ArrayDeepCopy
{
ByteArrayOutputStream baos;
ByteArrayInputStream bins;
public void saveState(Object obj)throws Exception //saving the stream of bytes of object to `ObjectOutputStream`.
{
baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
}
public int[][] readState()throws Exception //reading the state back to object using `ObjectInputStream`
{
bins = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream oins = new ObjectInputStream(bins);
Object obj = oins.readObject();
oins.close();
return (int[][])obj;
}
public static void main(String[] args) throws Exception
{
int arr[][]= {
{1,2,3},
{4,5,7}
};
ArrayDeepCopy ars = new ArrayDeepCopy();
System.out.println("Saving state...");
ars.saveState(arr);
System.out.println("State saved..");
System.out.println("Retrieving state..");
int j[][] = ars.readState();
System.out.println("State retrieved..And the retrieved array is:");
for (int i =0 ; i < j.length ; i++ )
{
for (int k = 0 ; k < j[i].length ; k++)
{
System.out.print(j[i][k]+"\t");
}
System.out.print("\n");
}
}
}
clone crée une copie superficielle de l'objet, l'objet clone est toujours de la même classe que l'original. Tous les champs, privés ou non, sont copiés.
API BeanUtils.copyProperties Copiez les valeurs de propriété du bean d'origine vers le bean de destination pour tous les cas où les noms de propriété sont identiques.
Pour moi, ces deux concepts ont peu en commun.
J'ai vérifié le code source et j'ai découvert qu'il ne copiait que le "premier niveau" des propriétés primitives. Lorsqu'il s'agit d'un objet imbriqué, les propriétés imbriquées font toujours référence aux champs de l'objet d'origine, il ne s'agit donc pas d'une "copie complète".
Vérifiez ces extraits du code source Spring de org.springframework.beans.BeanUtils.Java
, Version 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);
}
}
}
}
}
}
Concentrez-vous sur cette ligne:
writeMethod.invoke(target, value);
Cette ligne appelle le setter sur l'objet cible. Imaginez cette classe:
class Student {
private String name;
private Address address;
}
Si nous avons student1
Et student2
, Le second est juste intancié et aucun champ n'est affecté, student1
A address1
Et nom John
.
Donc, si nous appelons:
BeanUtils.copyProperties(student1, student2);
Nous faisons:
student2.setName(student1.getName()); // this is copy because String is immutable
student2.setAddress(student1.getAddress()); // this is NOT copy, we still are referencing `address1`
Lorsque address1
Change, il change également student2
.
Ainsi, BeanUtils.copyProperties()
ne fonctionne que sur les champs de types primitifs de l'objet; s'il est imbriqué, cela ne fonctionne pas; ou, vous devez vous assurer de l'immuabilité de l'objet d'origine pendant tout le cycle de vie de l'objet cible, ce qui n'est ni facile ni souhaitable.
Si vous voulez vraiment en faire une copie complète, vous devez implémenter de manière récursive cette méthode sur des champs qui ne sont pas des primitives. Enfin, vous atteindrez une classe avec uniquement des champs primitifs/immuables et vous aurez terminé.
Le clonage est fait par vous. Si l'instance que vous essayez de cloner contient la référence d'une autre instance, vous devez également écrire du code de clonage dans celle-ci. Que faire si les instances contiennent une chaîne de références à d'autres instances? Donc, si vous faites le clonage par vous-même, il est possible que vous manquiez un petit détail.
BeanUtils.copyProperties, d'autre part, s'occupe de tout par lui-même. Cela réduit vos efforts.