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



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).


13
Anscheinend funktioniert BeanUtils nicht mit Null-Datumsfeldern. Verwenden Sie Apache PropertyUtils, wenn dies ein Problem für Sie ist: mail-archive.com/user@commons.apache.org/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);

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

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

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

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.



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!


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

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

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

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

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
}

}}


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

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

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>

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


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.