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 A
do CopyA
przed wysłaniem CopyA
przez 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 A
do 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ł
java
reflection
Shervin Asgari
źródło
źródło
Odpowiedzi:
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)
.źródło
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);
źródło
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; }
źródło
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; } }
źródło
i.remove()
. Nawet jeśli został utworzony iterator nie można dzwonićremove
naList
„siterator
. Powinno byćArrayList
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; }
źródło
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 docelowyfrom
).Większość interfejsu API refleksji działa w ten sposób. Masz
Field
przedmioty,Method
obiekty, i tak dalej, z klasą, a nie z instancją, więc z nich korzystać (z wyjątkiem statyki) generalnie trzeba przekazać im instancję.źródło
Dozer
AKTUALIZACJA, 19 listopada 2012: Jest też nowy projekt ModelMapper .
źródło
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 Common
zgł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!
źródło
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
źródło
Bez używania BeanUtils lub Apache Commons
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)); } }
źródło
Wiosna ma wbudowaną
BeanUtils.copyProperties
metodę. 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);
źródło
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);
źródło
SerializationUtils.clone()
da nowy obiekt tej samej klasy. Dodatkowo działa tylko na klasach możliwych do serializacji.Jeśli w zależnościach masz sprężynę, możesz również użyć org.springframework.beans.BeanUtils .
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html
źródło
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 }
}
źródło
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; }
źródło
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; }
źródło
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>
źródło
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
źródło