web-dev-qa-db-fra.com

Copier toutes les valeurs des champs d'une classe à une autre par réflexion

J'ai une classe qui est essentiellement une copie d'une autre classe.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Ce que je fais est de mettre des valeurs de la classe A dans CopyA avant d'envoyer CopyA par l'intermédiaire d'un appel de service Web. Maintenant, j'aimerais créer une méthode de réflexion qui copie tous les champs identiques (par leur nom et leur type) de la classe A vers la classe CopyA.

Comment puis-je faire ceci?

C’est ce que j’ai eu jusqu’à présent, mais cela ne fonctionne pas tout à fait. Je pense que le problème ici est que je suis en train d'essayer de définir un champ sur le terrain que je boucle en boucle.

private <T extends Object, Y extends Object> void copyFields(T from, Y too) {

    Class<? extends Object> fromClass = from.getClass();
    Field[] fromFields = fromClass.getDeclaredFields();

    Class<? extends Object> tooClass = too.getClass();
    Field[] tooFields = tooClass.getDeclaredFields();

    if (fromFields != null && tooFields != null) {
        for (Field tooF : tooFields) {
            logger.debug("toofield name #0 and type #1", tooF.getName(), tooF.getType().toString());
            try {
                // Check if that fields exists in the other method
                Field fromF = fromClass.getDeclaredField(tooF.getName());
                if (fromF.getType().equals(tooF.getType())) {
                    tooF.set(tooF, fromF);
                }
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

Je suis sûr qu'il doit y avoir quelqu'un qui a déjà fait cela d'une manière ou d'une autre

71
Shervin Asgari

Si cela ne vous dérange pas d'utiliser une bibliothèque tierce, BeanUtils d'Apache Commons gérera cela assez facilement, en utilisant copyProperties(Object, Object).

83
Greg Case

Pourquoi n'utilisez-vous pas la bibliothèque gson https://github.com/google/gson

vous venez de convertir la classe A en chaîne json. Puis convertissez jsonString en votre sous-classe (CopyA). Utilisez le code ci-dessous:

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);
19
Eric Ho

BeanUtils ne copie que les champs publics et est un peu lent. Allez plutôt avec les méthodes getter et setter.

public Object loadData (RideHotelsService object_a) throws Exception{

        Method[] gettersAndSetters = object_a.getClass().getMethods();

        for (int i = 0; i < gettersAndSetters.length; i++) {
                String methodName = gettersAndSetters[i].getName();
                try{
                  if(methodName.startsWith("get")){
                     this.getClass().getMethod(methodName.replaceFirst("get", "set") , gettersAndSetters[i].getReturnType() ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }else if(methodName.startsWith("is") ){
                            this.getClass().getMethod(methodName.replaceFirst("is", "set") ,  gettersAndSetters[i].getReturnType()  ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }

                }catch (NoSuchMethodException e) {
                    // TODO: handle exception
                }catch (IllegalArgumentException e) {
                    // TODO: handle exception
                }

        }

        return null;
    }
8
Supun Sameera

bulldozer

UPDATE 19 nov 2012: Il y a maintenant un nouveau projet ModelMapper aussi.

4
Ruben Bartelink

Le premier argument de tooF.set() devrait être l'objet cible (too), pas le champ, et le deuxième argument devrait être le valeur, pas le champ la valeur vient de. (Pour obtenir la valeur, vous devez appeler fromF.get() - en passant à nouveau un objet cible, dans ce cas from.)

La plupart des API de réflexion fonctionnent de cette façon. Vous obtenez Field objets, Method objets, etc. de la classe et non d'une instance. Par conséquent, pour les utiliser (à l'exception de la statique), vous devez généralement leur transmettre une instance.

4
David Moles

Voici une solution de travail et testée. Vous pouvez contrôler la profondeur du mappage dans la hiérarchie de classes.

public class FieldMapper {

    public static void copy(Object from, Object to) throws Exception {
        FieldMapper.copy(from, to, Object.class);
    }

    public static void copy(Object from, Object to, Class depth) throws Exception {
        Class fromClass = from.getClass();
        Class toClass = to.getClass();
        List<Field> fromFields = collectFields(fromClass, depth);
        List<Field> toFields = collectFields(toClass, depth);
        Field target;
        for (Field source : fromFields) {
            if ((target = findAndRemove(source, toFields)) != null) {
                target.set(to, source.get(from));
            }
        }
    }

    private static List<Field> collectFields(Class c, Class depth) {
        List<Field> accessibleFields = new ArrayList<>();
        do {
            int modifiers;
            for (Field field : c.getDeclaredFields()) {
                modifiers = field.getModifiers();
                if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
                    accessibleFields.add(field);
                }
            }
            c = c.getSuperclass();
        } while (c != null && c != depth);
        return accessibleFields;
    }

    private static Field findAndRemove(Field field, List<Field> fields) {
        Field actual;
        for (Iterator<Field> i = fields.iterator(); i.hasNext();) {
            actual = i.next();
            if (field.getName().equals(actual.getName())
                && field.getType().equals(actual.getType())) {
                i.remove();
                return actual;
            }
        }
        return null;
    }
}
4
JHead
  1. Sans utiliser BeanUtils ou Apache Commons

  2. public static <T1 extends Object, T2 extends Object>  void copy(T1     
    origEntity, T2 destEntity) throws IllegalAccessException, NoSuchFieldException {
        Field[] fields = origEntity.getClass().getDeclaredFields();
        for (Field field : fields){
            origFields.set(destEntity, field.get(origEntity));
         }
    }
    
4
Darkhan Iskakov

Ma solution:

public static <T > void copyAllFields(T to, T from) {
        Class<T> clazz = (Class<T>) from.getClass();
        // OR:
        // Class<T> clazz = (Class<T>) to.getClass();
        List<Field> fields = getAllModelFields(clazz);

        if (fields != null) {
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    field.set(to,field.get(from));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

public static List<Field> getAllModelFields(Class aClass) {
    List<Field> fields = new ArrayList<>();
    do {
        Collections.addAll(fields, aClass.getDeclaredFields());
        aClass = aClass.getSuperclass();
    } while (aClass != null);
    return fields;
}
3
Mohsen Kashi

Je pense que vous pouvez essayer bulldozer . Il a un bon support pour la conversion de haricot à haricot. C'est aussi facile à utiliser. Vous pouvez l'injecter dans votre application Spring ou ajouter le fichier jar dans le chemin d'accès aux classes et son exécution.

Pour un exemple de votre cas:

 DozerMapper mapper = new DozerMapper();
A a= new A();
CopyA copyA = new CopyA();
a.set... // set fields of a.
mapper.map(a,copyOfA); // will copy all fields from a to copyA
3
Priyank Doshi

Le printemps a construit en BeanUtils.copyProperties méthode. Mais cela ne fonctionne pas avec des classes sans accesseurs/régleurs. La sérialisation/désérialisation JSON peut être une autre option pour la copie de champs. Jackson peut être utilisé à cette fin. Si vous utilisez Spring Dans la plupart des cas, Jackson est déjà dans votre liste de dépendances.

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);
2
Fırat KÜÇÜK

Ceci est un message tardif, mais peut toujours être efficace pour les gens à l'avenir.

Spring fournit un utilitaire BeanUtils.copyProperties(srcObj, tarObj) qui copie les valeurs d'un objet source vers un objet cible lorsque les noms des variables membres des deux classes sont identiques.

S'il existe une conversion de date (par exemple, Chaîne en date), "null" sera copié dans l'objet cible. Nous pouvons ensuite définir explicitement les valeurs de la date selon les besoins.

Les BeanUtils de Apache Common renvoie une erreur en cas de non concordance des types de données (notamment lors de la conversion vers et à partir de la date)

J'espère que cela t'aides!

2
Nicholas K

Si vous avez un ressort dans les dépendances, vous pouvez également utiliser org.springframework.beans.BeanUtils .

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html

1
db80

Ouais ou les BeanUtils de Apache Jakarta.

1
Shaun F

Orika est un simple cadre de cartographie de bean plus rapide car il le fait via la génération de code octet. Il effectue des mappages imbriqués et des mappages avec des noms différents. Pour plus de détails, veuillez cocher ici La cartographie des échantillons peut sembler complexe, mais pour des scénarios complexes, ce serait simple.

MapperFactory factory = new DefaultMapperFactory.Builder().build();
mapperFactory.registerClassMap(mapperFactory.classMap(Book.class,BookDto.class).byDefault().toClassMap());
MapperFacade mapper = factory.getMapperFacade();
BookDto bookDto = mapperFacade.map(book, BookDto.class);
1
Nagappan

Pour cette raison, je ne voulais pas ajouter de dépendance à un autre fichier JAR. J'ai donc écrit quelque chose qui conviendrait à mes besoins. Je suis la convention de fjorm https://code.google.com/p/fjorm/ , ce qui signifie que mes champs généralement accessibles sont publics et que je ne me soucie pas d'écrire des setters et des getters. (à mon avis le code est plus facile à gérer et plus lisible réellement)

J'ai donc écrit quelque chose (ce n'est pas vraiment très difficile) qui convient à mes besoins (en supposant que la classe a un constructeur public sans argument) et il pourrait être extrait dans une classe d'utilitaires

  public Effect copyUsingReflection() {
    Constructor constructorToUse = null;
    for (Constructor constructor : this.getClass().getConstructors()) {
      if (constructor.getParameterTypes().length == 0) {
        constructorToUse = constructor;
        constructorToUse.setAccessible(true);
      }
    }
    if (constructorToUse != null) {
      try {
        Effect copyOfEffect = (Effect) constructorToUse.newInstance();
        for (Field field : this.getClass().getFields()) {
          try {
            Object valueToCopy = field.get(this);
            //if it has field of the same type (Effect in this case), call the method to copy it recursively
            if (valueToCopy instanceof Effect) {
              valueToCopy = ((Effect) valueToCopy).copyUsingReflection();
            }
            //TODO add here other special types of fields, like Maps, Lists, etc.
            field.set(copyOfEffect, valueToCopy);
          } catch (IllegalArgumentException | IllegalAccessException ex) {
            Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
        return copyOfEffect;
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return null;
  }
0
Mladen Adamovic