Kopieren Sie alle Werte durch Reflektion von Feldern in einer Klasse in eine andere

82

Ich habe eine Klasse, die im Grunde eine Kopie einer anderen Klasse ist.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Was ich tue , ist , Werte aus der Klasse setzen Ain CopyAvor dem Senden CopyAüber einen Webservice Aufruf. Jetzt möchte ich eine Reflexionsmethode erstellen, die grundsätzlich alle Felder kopiert, die (nach Name und Typ) von Klasse Azu Klasse identisch sind CopyA.

Wie kann ich das machen?

Das habe ich bisher, aber es funktioniert nicht ganz. Ich denke, das Problem hier ist, dass ich versuche, ein Feld auf dem Feld zu setzen, das ich durchlaufe.

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();
            }

        }
    }

Ich bin sicher, es muss jemanden geben, der das schon irgendwie gemacht hat

Shervin Asgari
quelle
2
Siehe auch stackoverflow.com/questions/1432764/…
Ruben Bartelink
Ja oder die BeanUtils von Apache Jakarta.
Shaun F

Antworten:

102

Wenn es Ihnen nichts ausmacht, eine Bibliothek eines Drittanbieters zu verwenden, können BeanUtils von Apache Commons dies ganz einfach verwenden copyProperties(Object, Object).

Greg Case
quelle
13
Anscheinend funktioniert BeanUtils nicht mit Null-Datumsfeldern. Verwenden Sie Apache PropertyUtils, wenn dies ein Problem für Sie ist: mail-archive.com/[email protected]/msg02246.html
ripper234
10
Dies funktioniert anscheinend nicht für private Felder ohne Getter und Setter. Gibt es eine Lösung, die direkt mit Feldern und nicht mit Eigenschaften funktioniert?
Andrea Ratto
Es funktioniert auch nicht mit einfachen öffentlichen Feldern ohne Getter: stackoverflow.com/questions/34263122/…
Vadzim
17

Warum verwenden Sie nicht die gson-Bibliothek https://github.com/google/gson ?

Sie konvertieren einfach die Klasse A in einen JSON-String. Konvertieren Sie dann jsonString in Ihre Unterklasse (CopyA). Verwenden Sie dazu den folgenden Code:

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);
Eric Ho
quelle
Warum einen anderen String generieren, der auch groß sein kann? Es gibt bessere Alternativen, die hier als Antworten beschrieben werden. Zumindest sind wir (die Branche) für Zeichenfolgendarstellungen von XML zu json übergegangen, aber wir möchten immer noch nicht, dass bei jeder Gelegenheit alles an diese Zeichenfolgendarstellung übergeben wird ...
arntg
Bitte beachten Sie, dass die Saite ein Nebenprodukt bei der Verwendung von Reflexion ist. Auch durch dich hast du es nicht gerettet !! Dies ist eine Antwort für Java-Anfänger und zielt darauf ab, kurz und sauber zu sein. @arntg
Eric Ho
Sie müssten immer noch über das Quell- und das Zielobjekt nachdenken, aber jetzt führen Sie auch die Binär- / Text- / Binärformatierung und den Parsing-Overhead ein.
Evvo
Achten Sie darauf, wenn Sie den Code mit Proguard verschleiern. Wenn Sie es verwenden, funktioniert dieser Code nicht.
SebastiaoRealino
8

BeanUtils kopiert nur öffentliche Felder und ist etwas langsam. Gehen Sie stattdessen mit Getter- und Setter-Methoden.

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;
    }
Supun Sameera
quelle
BeanUtils funktioniert gut auf privaten Feldern, solange die Getter / Setter öffentlich sind. In Bezug auf die Leistung habe ich kein Benchmarking durchgeführt, aber ich glaube, dass es ein internes Caching der Beans durchführt, die es selbst untersucht hat.
Greg Case
2
Dies funktioniert nur, wenn die beiden Beans denselben Datentyp von Feldern haben.
TimeToCodeTheRoad
@ To Kra Es würde nur funktionieren, wenn Sie Getter / Setter für dieses Feld haben.
WoLfPwNeR
7

Hier ist eine funktionierende und getestete Lösung. Sie können die Tiefe der Zuordnung in der Klassenhierarchie steuern.

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;
    }
}
JHead
quelle
1
Ich habe eine ähnliche Lösung erstellt. Ich habe eine Klasse zwischen Feldnamen und Feldzuordnung zwischengespeichert.
Orden
Die Lösung ist nett, aber Sie werden ein Problem in dieser Zeile haben i.remove(). Auch wenn Sie Iterator erstellt haben , können Sie nicht rufen Sie removeauf List‚s iterator. Es sollte seinArrayList
Farid
Farid, remove kann kein Problem sein, da collectFields () ArrayList-Objekte erstellt.
JHead
5

Meine Lösung:

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;
}
Mohsen Kashi
quelle
Ich denke nicht, dass dies für benutzerdefinierte Objekte nicht funktioniert. Nur wenn Sie eine Klasse ohne Eltern und nur primitive Felder haben
Shervin Asgari
Zum Abdecken von Superklassenfeldern verwende ich die benutzerdefinierte Methode 'getAllModelFields'
Mohsen Kashi
4

Das erste Argument tooF.set()sollte das Zielobjekt ( too) sein, nicht das Feld, und das zweite Argument sollte der Wert sein , nicht das Feld, aus dem der Wert stammt. (Um den Wert zu erhalten, müssen Sie aufrufen fromF.get()und in diesem Fall erneut ein Zielobjekt übergeben from.)

Die meisten Reflection-APIs funktionieren auf diese Weise. Sie erhalten FieldObjekte, MethodObjekte usw. von der Klasse, nicht von einer Instanz. Um sie zu verwenden (mit Ausnahme der Statik), müssen Sie ihnen im Allgemeinen eine Instanz übergeben.

David Moles
quelle
4

Dies ist ein später Beitrag, kann aber auch in Zukunft für Menschen wirksam sein.

Spring bietet ein Dienstprogramm, BeanUtils.copyProperties(srcObj, tarObj)das Werte vom Quellobjekt zum Zielobjekt kopiert, wenn die Namen der Elementvariablen beider Klassen identisch sind.

Bei einer Datumskonvertierung (z. B. String to Date) wird 'null' in das Zielobjekt kopiert. Wir können dann die Werte des Datums explizit nach Bedarf einstellen.

Die BeanUtils von geben Apache Commoneinen Fehler aus, wenn die Datentypen nicht übereinstimmen (insbesondere die Konvertierung von und nach Datum).

Hoffe das hilft!

Nicholas K.
quelle
Dies bietet keine zusätzlichen Informationen als akzeptierte stackoverflow.com/a/1667911/4589003 Antwort
Sudip Bhandari
3

Ich denke, Sie können Bulldozer versuchen . Es bietet eine gute Unterstützung für die Umwandlung von Bohnen in Bohnen. Es ist auch einfach zu bedienen. Sie können es entweder in Ihre Federanwendung injizieren oder das Glas in den Klassenpfad einfügen und fertig.

Ein Beispiel für Ihren Fall:

 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
Priyank Doshi
quelle
3
  1. Ohne BeanUtils oder 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));
         }
    }
    
Darkhan Iskakov
quelle
Dies ist keine funktionierende Lösung, aber ein guter Ausgangspunkt. Die Felder müssen gefiltert werden, um nur die nicht statischen und öffentlichen Felder zu verarbeiten, die in beiden Klassen vorhanden sind.
JHead
Würde dies nicht Felder in übergeordneten Klassen ignorieren?
Evvo
2

Der Frühling hat eine eingebaute BeanUtils.copyPropertiesMethode. Aber es funktioniert nicht mit Klassen ohne Getter / Setter. JSON-Serialisierung / Deserialisierung kann eine weitere Option zum Kopieren von Feldern sein. Jackson kann für diesen Zweck verwendet werden. Wenn Sie Spring verwenden In den meisten Fällen befindet sich Jackson bereits in Ihrer Abhängigkeitsliste.

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

Orika's ist ein einfaches, schnelleres Bean-Mapping-Framework, da es durch Bytecode-Generierung funktioniert. Es werden verschachtelte Zuordnungen und Zuordnungen mit unterschiedlichen Namen ausgeführt. Weitere Informationen finden Sie hier. Die Beispielzuordnung mag komplex aussehen, bei komplexen Szenarien wäre sie jedoch einfach.

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);
Nagappan
quelle
Dies macht nicht das, wonach die Frage verlangt. SerializationUtils.clone()wird ein neues Objekt der gleichen Klasse geben. Außerdem funktioniert es nur mit serialisierbaren Klassen.
Kirby
1

Ich habe das oben genannte Problem in Kotlin gelöst, das für meine Android Apps-Entwicklung gut funktioniert:

 object FieldMapper {

fun <T:Any> copy(to: T, from: T) {
    try {
        val fromClass = from.javaClass

        val fromFields = getAllFields(fromClass)

        fromFields?.let {
            for (field in fromFields) {
                try {
                    field.isAccessible = true
                    field.set(to, field.get(from))
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                }

            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }

}

private fun getAllFields(paramClass: Class<*>): List<Field> {

    var theClass:Class<*>? = paramClass
    val fields = ArrayList<Field>()
    try {
        while (theClass != null) {
            Collections.addAll(fields, *theClass?.declaredFields)
            theClass = theClass?.superclass
        }
    }catch (e:Exception){
        e.printStackTrace()
    }

    return fields
}

}}

Babul Mirdha
quelle
0

Aus diesem Grund wollte ich keine Abhängigkeit zu einer anderen JAR-Datei hinzufügen, also habe ich etwas geschrieben, das meinen Anforderungen entspricht. Ich folge der Konvention von fjorm https://code.google.com/p/fjorm/ was bedeutet, dass meine allgemein zugänglichen Felder öffentlich sind und ich mir nicht die Mühe mache, Setter und Getter zu schreiben. (Meiner Meinung nach ist der Code einfacher zu verwalten und tatsächlich besser lesbar.)

Also habe ich etwas geschrieben (es ist eigentlich nicht sehr schwierig), das meinen Anforderungen entspricht (vorausgesetzt, die Klasse hat einen öffentlichen Konstruktor ohne Argumente) und es könnte in eine Utility-Klasse extrahiert werden

  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;
  }
Mladen Adamovic
quelle
Antipattern: Rad neu
erfinden
0

Die Grundidee von Mladen hat funktioniert (danke), aber es waren einige Änderungen erforderlich, um robust zu sein. Deshalb habe ich sie hier beigesteuert.

Der einzige Ort, an dem diese Art von Lösung verwendet werden sollte, ist, wenn Sie das Objekt klonen möchten, dies jedoch nicht können, da es sich um ein verwaltetes Objekt handelt. Wenn Sie das Glück haben, Objekte zu haben, die alle 100% nebenwirkungsfreie Setter für alle Felder haben, sollten Sie stattdessen auf jeden Fall die Option BeanUtils verwenden.

Hier verwende ich die Dienstprogrammmethoden von lang3, um den Code zu vereinfachen. Wenn Sie ihn einfügen, müssen Sie zuerst die lang3-Bibliothek von Apache importieren.

Code kopieren

static public <X extends Object> X copy(X object, String... skipFields) {
        Constructor constructorToUse = null;
        for (Constructor constructor : object.getClass().getConstructors()) {
            if (constructor.getParameterTypes().length == 0) {
                constructorToUse = constructor;
                constructorToUse.setAccessible(true);
                break;
            }
        }
        if (constructorToUse == null) {
            throw new IllegalStateException(object + " must have a zero arg constructor in order to be copied");
        }
        X copy;
        try {
            copy = (X) constructorToUse.newInstance();

            for (Field field : FieldUtils.getAllFields(object.getClass())) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }

                //Avoid the fields that you don't want to copy. Note, if you pass in "id", it will skip any field with "id" in it. So be careful.
                if (StringUtils.containsAny(field.getName(), skipFields)) {
                    continue;
                }

                field.setAccessible(true);

                Object valueToCopy = field.get(object);
                //TODO add here other special types of fields, like Maps, Lists, etc.
                field.set(copy, valueToCopy);

            }

        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new IllegalStateException("Could not copy " + object, e);
        }
        return copy;
}
Emily Crutcher
quelle
0
    public <T1 extends Object, T2 extends Object> void copy(T1 origEntity, T2 destEntity) {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.map(origEntity,destEntity);
    }

 <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.4.0</version>
        </dependency>
Ran Adler
quelle
Diese mapper.map hat Probleme, in einer Entität werden die Primärschlüssel nicht kopiert
Mohammed Rafeeq
0
public static <T> void copyAvalableFields(@NotNull T source, @NotNull T target) throws IllegalAccessException {
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (!Modifier.isStatic(field.getModifiers())
                && !Modifier.isFinal(field.getModifiers())) {
            field.set(target, field.get(source));
        }
    }
}

Wir lesen alle Felder der Klasse. Filtern Sie nicht statische und nicht endgültige Felder aus dem Ergebnis. Es kann jedoch ein Fehler beim Zugriff auf nicht öffentliche Felder auftreten. Wenn sich diese Funktion beispielsweise in derselben Klasse befindet und die zu kopierende Klasse keine öffentlichen Felder enthält, tritt ein Zugriffsfehler auf. Die Lösung kann darin bestehen, diese Funktion im selben Paket zu platzieren oder den Zugriff auf public oder in diesem Code innerhalb des Schleifenaufruffelds zu ändern. SetAccessible (true); Was macht die Felder verfügbar

Zedong
quelle
Obwohl dieser Code eine Lösung für die Frage bietet, ist es besser, einen Kontext hinzuzufügen, warum / wie er funktioniert. Dies kann zukünftigen Benutzern helfen, zu lernen und dieses Wissen auf ihren eigenen Code anzuwenden. Es ist auch wahrscheinlich, dass Sie positive Rückmeldungen von Benutzern in Form von Upvotes erhalten, wenn der Code erklärt wird.
Borchvm