Skopiuj wszystkie wartości z pól jednej klasy do drugiej poprzez odbicie


82

Mam klasę, która jest w zasadzie kopią innej klasy.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Co robię jest wprowadzenie wartości z klasy Ado CopyAprzed wysłaniem CopyAprzez wywołanie WebService. Teraz chciałbym stworzyć metodę odbicia, która w zasadzie kopiuje wszystkie pola, które są identyczne (według nazwy i typu) z klasy Ado klasy CopyA.

Jak mogę to zrobić?

To, co mam do tej pory, ale nie do końca działa. Myślę, że problem polega na tym, że próbuję ustawić pole na polu, przez które przechodzę.

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

        }
    }

Jestem pewien, że musi być ktoś, kto już to zrobił



Tak lub BeanUtils z Apache Jakarta.
Shaun F

Odpowiedzi:


102

Jeśli nie masz nic przeciwko korzystaniu z biblioteki innej firmy, BeanUtils z Apache Commons poradzi sobie z tym dość łatwo, używając copyProperties(Object, Object).


13
Najwyraźniej BeanUtils nie działa z pustymi polami Date. Użyj Apache PropertyUtils, jeśli stanowi to dla Ciebie problem: mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
Najwyraźniej nie działa to na prywatnych polach bez getterów i seterów. Jakieś rozwiązanie, które działa bezpośrednio z polami, a nie z właściwościami?
Andrea Ratto

Nie działa również ze zwykłymi polami publicznymi bez getterów: stackoverflow.com/questions/34263122/…
Vadzim

17

Dlaczego nie używasz biblioteki gson https://github.com/google/gson

po prostu konwertujesz klasę A na ciąg json. Następnie przekonwertuj jsonString na you subClass (CopyA). Używając poniższego kodu:

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

Po co generować kolejny ciąg, który również mógłby być duży? Istnieją lepsze alternatywy opisane tutaj jako odpowiedzi. Przynajmniej my (branża) przeszliśmy z XML do json dla reprezentacji ciągów, ale nadal nie chcemy, aby wszystko było przekazywane do tej reprezentacji ciągu przy każdej okazji ...
arntg

Proszę zauważyć, że sznurek jest produktem ubocznym podczas korzystania z odblasków. Nawet przez ciebie nie uratowałeś tego !! To jest odpowiedź dla początkujących użytkowników Java i staraj się, aby była krótka i przejrzysta. @arntg
Eric Ho,

Nadal będziesz potrzebować refleksji zarówno nad obiektami źródłowymi, jak i docelowymi, ale teraz wprowadzasz również formatowanie binarne / tekstowe / binarne i narzut przetwarzania.
Evvo

Zwróć uwagę, jeśli używasz Proguard do zaciemniania kodu. Jeśli go użyjesz, ten kod nie zadziała.
SebastiaoRealino

8

BeanUtils kopiuje tylko pola publiczne i działa trochę wolno. Zamiast tego skorzystaj z metod pobierających i ustawiających.

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 działa dobrze na polach prywatnych, o ile metody pobierające / ustawiające są publiczne. Jeśli chodzi o wydajność, nie przeprowadziłem żadnych testów porównawczych, ale wydaje mi się, że wykonuje to wewnętrzne buforowanie fasoli, którą przeszedł introspekcję.
Greg Case,

2
zadziała to tylko wtedy, gdy dwa ziarna mają ten sam typ danych w polach.
TimeToCodeTheRoad

@To Kra Działa to tylko wtedy, gdy masz getter / setter dla tego pola.
WoLfPwNeR

8

Oto działające i sprawdzone rozwiązanie. Możesz kontrolować głębokość mapowania w hierarchii klas.

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
Stworzyłem podobne rozwiązanie. Zapisałem klasę do nazw pól do mapy pól.
Orden

Rozwiązanie jest fajne, ale będziesz miał problem w tej linii i.remove(). Nawet jeśli został utworzony iterator nie można dzwonić removena List„s iterator. Powinno byćArrayList
Farid

Farid, remove nie może być problemem, ponieważ collectFields () tworzy obiekty ArrayList.
JHead

5

Moje rozwiązanie:

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

Myślę, że to nie działa w przypadku obiektów niestandardowych. Tylko jeśli masz klasę bez rodzica i tylko prymitywne pola
Shervin Asgari,

Do pokrycia pól superklasy używam niestandardowej metody „getAllModelFields”
Mohsen Kashi

4

Pierwszym argumentem tooF.set()powinien być obiekt docelowy ( too), a nie pole, a drugim argumentem powinna być wartość , a nie pole, z którego pochodzi wartość. (Aby uzyskać wartość, musisz wywołać fromF.get()- w tym przypadku ponownie przekazując obiekt docelowy from).

Większość interfejsu API refleksji działa w ten sposób. Masz Fieldprzedmioty, Methodobiekty, i tak dalej, z klasą, a nie z instancją, więc z nich korzystać (z wyjątkiem statyki) generalnie trzeba przekazać im instancję.



4

To jest późny post, ale nadal może być skuteczny dla ludzi w przyszłości.

Wiosna zapewnia narzędzie BeanUtils.copyProperties(srcObj, tarObj) które kopiuje wartości z obiektu źródłowego do obiektu docelowego, gdy nazwy zmiennych składowych obu klas są takie same.

W przypadku konwersji daty (np. String na Date) wartość „null” zostanie skopiowana do obiektu docelowego. Możemy wtedy jawnie ustawić wartości daty zgodnie z wymaganiami.

BeanUtils from Apache Commonzgłasza błąd, gdy występuje niezgodność typów danych (w szczególności konwersja do i z daty)

Mam nadzieję że to pomoże!


To nie dostarcza żadnych dodatkowych informacji poza zaakceptowaną odpowiedzią stackoverflow.com/a/1667911/4589003
Sudip Bhandari

3

Myślę, że możesz spróbować spycharki . Ma dobre wsparcie dla konwersji ziaren w ziarna. Jest również łatwy w użyciu. Możesz wstrzyknąć go do aplikacji sprężynowej lub dodać jar w ścieżce klas i gotowe.

Na przykład twojego przypadku:

 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. Bez używania BeanUtils lub 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));
         }
    }
    

To nie jest działające rozwiązanie, ale dobry punkt wyjścia. Pola muszą być filtrowane, aby obsługiwały tylko pola niestatyczne i publiczne, które są obecne w obu klasach.
JHead

Czy nie zignorowałoby to pól w klasach nadrzędnych?
Evvo

2

Wiosna ma wbudowaną BeanUtils.copyPropertiesmetodę. Ale nie działa z klasami bez getterów / seterów. Serializacja / deserializacja JSON może być inną opcją kopiowania pól. Do tego celu można użyć Jacksona. Jeśli używasz Springa W większości przypadków Jackson jest już na Twojej liście zależności.

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);

1

Orika jest prostym, szybszym frameworkiem do mapowania bean, ponieważ działa poprzez generowanie kodu bajtowego. Wykonuje zagnieżdżone mapowania i mapowania o różnych nazwach. Aby uzyskać więcej informacji, sprawdź tutaj Przykładowe mapowanie może wyglądać na złożone, ale w przypadku złożonych scenariuszy byłoby proste.

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

To nie spełnia tego, o co chodzi w pytaniu. SerializationUtils.clone()da nowy obiekt tej samej klasy. Dodatkowo działa tylko na klasach możliwych do serializacji.
Kirby


1

Rozwiązałem powyższy problem w Kotlinie, który działa dobrze dla mnie w przypadku mojego rozwoju aplikacji na Androida:

 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

Z tego powodu nie chciałem dodawać zależności do innego pliku JAR, więc napisałem coś, co odpowiadałoby moim potrzebom. Przestrzegam konwencji fjorm https://code.google.com/p/fjorm/, co oznacza, że ​​moje ogólnie dostępne pola są publiczne i nie zawracam sobie głowy pisaniem seterów i pobierających. (moim zdaniem kod jest łatwiejszy w zarządzaniu i właściwie bardziej czytelny)

Więc napisałem coś (właściwie nie jest to trudne), co odpowiada moim potrzebom (zakłada, że ​​klasa ma publiczny konstruktor bez argumentów) i można to wyodrębnić do klasy użytkowej

  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: Odkryj koło na nowo
Spektakulatius

0

Podstawowy pomysł Mladena zadziałał (dzięki), ale potrzebował kilku zmian, aby był solidny, więc dodałem je tutaj.

Jedynym miejscem, w którym należy użyć tego typu rozwiązania, jest sklonowanie obiektu, ale nie można tego zrobić, ponieważ jest to obiekt zarządzany. Jeśli masz szczęście, że masz obiekty, z których wszystkie mają 100% ustawiania bez efektów ubocznych dla wszystkich pól, zdecydowanie powinieneś zamiast tego użyć opcji BeanUtils.

Tutaj używam metod narzędziowych lang3, aby uprościć kod, więc jeśli go wkleisz, musisz najpierw zaimportować bibliotekę lang3 Apache.

Skopiuj kod

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>

Ten mapper.map ma problemy, w encji nie kopiuje kluczy podstawowych
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));
        }
    }
}

Przeczytaliśmy wszystkie pola zajęć. Filtruj z wyniku pola niestatyczne i inne niż końcowe. Ale może wystąpić błąd podczas uzyskiwania dostępu do pól niepublicznych. Na przykład, jeśli ta funkcja należy do tej samej klasy, a kopiowana klasa nie zawiera pól publicznych, wystąpi błąd dostępu. Rozwiązaniem może być umieszczenie tej funkcji w tym samym pakiecie lub zmiana dostępu na publiczny lub w tym kodzie wewnątrz pola wywołania pętli. SetAccessible (true); co udostępni pola


Chociaż ten kod może zapewnić rozwiązanie tego pytania, lepiej jest dodać kontekst, dlaczego / jak to działa. Może to pomóc przyszłym użytkownikom w nauce i zastosowaniu tej wiedzy we własnym kodzie. Prawdopodobnie otrzymasz również pozytywne opinie użytkowników w postaci głosów za, kiedy kod zostanie wyjaśniony.
borchvm
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.