2009-11-03 6 views
54

Mam klasę, która jest w zasadzie kopią innej klasy.Skopiuj wszystkie wartości z pól w jednej klasie do drugiej poprzez odbicie

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 drodze zaproszenia WebService. Teraz chciałbym stworzyć metodę refleksyjną, która zasadniczo kopiuje wszystkie pola, które są identyczne (według nazwy i typu) z klasy A do klasy CopyA.

Jak mogę to zrobić?

To jest to, co do tej pory miałem, ale to nie 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ż zrobił to jakoś

+2

Zobacz również http://stackoverflow.com/questions/1432764/any-tool-for-java-object-to-object-mapping –

Odpowiedz

63

Jeśli nie masz nic przeciwko korzystaniu z biblioteki stron trzecich, BeanUtils z Apache Commons poradzi sobie z tym całkiem łatwo, używając copyProperties(Object, Object).

+10

najwyraźniej BeanUtils nie działa null data dziedzinach. Użyj Apache PropertyUtils, jeśli jest to problem dla Ciebie: http://www.mail-archive.com/[email protected]/msg02246.html – ripper234

+6

To najwyraźniej nie działa dla prywatnych pól bez gettera i ustawiaczy. Jakieś rozwiązanie działające bezpośrednio na polach, a nie na właściwościach? –

1

Tak lub BeanUtils z Apache Jakarta.

4

Pierwszym argumentem tooF.set() należy obiekt docelowy (too), a nie w polu, a drugi argument powinien mieć wartość , a nie polu wartość pochodzi. (Aby uzyskać wartość, musisz zadzwonić pod numer fromF.get() - ponownie przekazując obiekt docelowy, w tym przypadku from.)

Większość interfejsu API do analizy działa w ten sposób. Otrzymujesz obiekty Field, obiekty Method itd. Z klasy, a nie z instancji, więc aby z nich korzystać (z wyjątkiem statyki), zazwyczaj musisz przekazać im instancję.

7

BeanUtils skopiuje tylko pola publiczne i jest nieco powolny. Zamiast tego idź z metodami getter i 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; 
    } 
+0

BeanUtils działa dobrze na prywatnych polach, o ile pliki pobierające/ustawiające są publiczne. Jeśli chodzi o wydajność, nie przeprowadziłem żadnego testu porównawczego, ale uważam, że robi to pewne wewnętrzne buforowanie komponentów bean, które zgłębił. –

+2

to działa tylko wtedy, gdy dwa komponenty bean mają taki sam typ pól danych. – TimeToCodeTheRoad

+0

Próbowałem i nie działa dla pól prywatnych –

3

Myślę, że możesz spróbować dozer. Ma dobre wsparcie dla konwersji fasoli na fasolę. Jest również łatwy w użyciu. Możesz wstrzyknąć go do swojej aplikacji wiosennej lub dodać słoik w ścieżce zajęć i gotowe.

Na przykład sprawy:

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 
0

Orika jest prosta szybciej ramy mapowanie fasola ponieważ robi poprzez generowanie kodu bajtowego. Tworzy zagnieżdżone odwzorowania i mapowania o różnych nazwach. Aby uzyskać więcej informacji, proszę check here Mapowanie próbek może wyglądać skomplikowanie, ale w przypadku złożonych scenariuszy byłoby to 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); 
+0

Nie robi to, o co pyta. 'SerializationUtils.clone()' da nowy obiekt tej samej klasy. Dodatkowo działa tylko na klasach nadających się do serializacji. – Kirby

+1

dostarczył nową odpowiedź po badaniu porównawczym z nową strukturą. – Nagappan

0

Nie chciałem, aby dodać zależność do innego pliku JAR z tego powodu, więc napisałem coś, co by pasowało do moich potrzeb.Postępuję zgodnie z konwencją fjorm https://code.google.com/p/fjorm/, co oznacza, że ​​moje ogólnie dostępne pola są publiczne i że nie zawracam sobie głowy pisaniem ustawiaczy i pobierających. (moim zdaniem kod jest łatwiejszy do zarządzania i bardziej czytelny).

Więc napisałem coś (to nie jest tak naprawdę trudne), które pasuje do moich potrzeb (zakłada, że ​​klasa ma publiczny konstruktor bez argumentów) i może zostać wyodrębnione do Klasa użyteczności

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; 
    } 
3
  1. Bez użycia 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)); 
        } 
    } 
    
+0

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

3

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

Nie sądzę, że to nie działa dla niestandardowych obiektów. Tylko jeśli masz klasę bez rodzica i tylko prymitywne pola –

+0

Do pokrywania pól klasy super używam metody niestandardowej getAllModelFields –

7

Dlaczego nie używacie gson biblioteka https://github.com/google/gson

po prostu przekształcić klasy A do json ciąg. Następnie przekonwertować jsonString do podklasy (CopyA) poniżej jakości wyników z użyciem kodu:

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

Wiosna posiada wbudowany BeanUtils.copyProperties metody. Ale nie działa z klasami bez modułów pobierających/ustawiających. Serializacja/deserializacja JSON może być inną opcją do kopiowania pól. Jackson może być używany do tego celu. 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

Oto działające i przetestowane 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; 
    } 
} 
Powiązane problemy