Скопіюйте всі значення з полів одного класу в інший шляхом роздумів


82

У мене є клас, який в основному є копією іншого класу.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Що я роблю, це введення значень з класу Aв CopyAперед відправкою CopyAчерез виклик веб-служби. Тепер я хотів би створити метод відображення, який в основному копіює всі ідентичні поля (за іменем та типом) з класу Aв клас CopyA.

Як я можу це зробити?

Це те, що я маю дотепер, але це не зовсім працює. Я думаю, що проблема тут у тому, що я намагаюся встановити поле на полі, яке я переглядаю.

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

        }
    }

Я впевнений, що повинен бути хтось, хто це вже якось зробив



Так, або BeanUtils від Apache Jakarta.
Shaun F

Відповіді:


103

Якщо ви не проти використовувати сторонню бібліотеку, BeanUtils від Apache Commons впорається з цим досить легко, використовуючи copyProperties(Object, Object).


13
Мабуть, BeanUtils не працює з полями нульової дати. Використовуйте Apache PropertyUtils, якщо для вас це проблема: mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
Очевидно, це не працює для приватних полів без геттера та сетерів. Будь-яке рішення, яке працює безпосередньо з полями, а не властивостями?
Андреа Ратто,

Воно не працює з простими громадськими полями без здобувачами: stackoverflow.com/questions/34263122 / ...
Vadzim

17

Чому б вам не використовувати бібліотеку gson https://github.com/google/gson

ви просто перетворюєте клас A у рядок json. Потім перетворіть jsonString на ваш підклас (CopyA). Використовуючи код нижче:

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

Навіщо генерувати інший рядок, який також може бути великим? Тут є кращі альтернативи, описані як відповіді. Принаймні ми (галузь) перейшли від XML до json для подання рядків, але ми все ще не хочемо, щоб все передавалося цьому
рядковому

Зверніть увагу, що струна є побічним продуктом при використанні відбиття. Навіть через вас це не врятували !! Це відповідь для початківців Java і прагне бути коротким та чистим. @arntg
Eric Ho

Вам все одно знадобляться роздуми як про джерело, так і про об’єкти призначення, але тепер ви також вводите двійкове / текстове / двійкове форматування та аналіз накладних витрат.
Evvo

Зверніть увагу, якщо ви використовуєте Proguard для затухання коду. Якщо ви використовуєте його, цей код не буде працювати.
SebastiaoRealino

8

BeanUtils копіюватиме лише загальнодоступні поля і трохи повільно. Замість цього скористайтеся методами getter та 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;
    }

BeanUtils чудово працює на приватних полях, доки користувачі / сетери є загальнодоступними. Щодо продуктивності, я не проводив жодного порівняльного аналізу, але я вважаю, що це робить деяке внутрішнє кешування фасолі, яку він проаналізував.
Справа Грега

2
це буде працювати лише в тому випадку, якщо два компоненти мають однаковий тип даних полів.
TimeToCodeTheRoad

@To Kra Це буде працювати, лише якщо у вас є getter / setter для цього поля.
WoLfPwNeR

8

Ось робоче і перевірене рішення. Ви можете контролювати глибину відображення в ієрархії класів.

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
Я створив подібне рішення. Я кешував клас до імен полів до карти полів.
Орден

Рішення хороше, але у вас буде проблема в цьому рядку i.remove(). Навіть якщо ви створили итератор ви не можете викликати removeна List«S iterator. Має бутиArrayList
Фарид

Farid, видалити не може бути проблемою, оскільки collectFields () створює об'єкти ArrayList.
JHead

5

Моє рішення:

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

Я не думаю, що це не працює для користувацьких об'єктів. Тільки якщо у вас є клас без батьків і лише примітивні поля
Шервін Асґарі

Для покриття полів суперкласу я використовую спеціальний метод 'getAllModelFields'
Mohsen Kashi

4

Першим аргументом tooF.set()повинно бути цільовий об'єкт ( too), а не поле, а другим аргументом має бути значення , а не поле, з якого походить значення. (Щоб отримати значення, вам потрібно зателефонувати fromF.get()- в цьому випадку знову передаючи цільовий об'єкт from.)

Більшість API відображення працює таким чином. Ви отримуєте Fieldоб'єкти, Methodоб'єкти тощо з класу, а не з екземпляра, тому для їх використання (за винятком статики) вам, як правило, потрібно передати екземпляр.



4

Це пізній допис, але все одно може бути ефективним для людей у ​​майбутньому.

Spring надає утиліту BeanUtils.copyProperties(srcObj, tarObj) яка копіює значення з вихідного об'єкта в цільовий об'єкт, коли імена змінних-членів обох класів однакові.

Якщо існує перетворення дати (наприклад, String to Date), значення "null" буде скопійовано в цільовий об'єкт. Тоді ми можемо чітко встановити значення дати за необхідності.

The BeanUtils від Apache Common видає помилку, коли виникає невідповідність типів даних (зокрема, перетворення в і з дати)

Сподіваюся, це допомагає!


Це не дає ніякої додаткової інформації , ніж загальноприйнятий stackoverflow.com/a/1667911/4589003 відповідь
Sudip Bhandari

3

Я думаю, ви можете спробувати бульдозер . Він має хорошу підтримку для перетворення бобу в боб. Він також простий у використанні. Ви можете або ввести його у свій весняний додаток, або додати банку в шлях до класу і все готово.

Для прикладу вашої справи:

 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. Без використання BeanUtils або 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));
         }
    }
    

Це не робоче рішення, а хороша відправна точка. Поля потрібно відфільтрувати, щоб обробляти лише нестатичні та загальнодоступні поля, присутні в обох класах.
JHead

Чи не буде це ігнорувати поля в батьківських класах?
Evvo

2

Весна має вбудований BeanUtils.copyPropertiesметод. Але це не працює з класами без геттерів / сеттерів. Серіалізація / десеріалізація JSON може бути ще одним варіантом копіювання полів. Для цього можна використовувати Джексона. Якщо ви використовуєте Spring, у більшості випадків Джексон вже є у вашому списку залежностей.

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

1

Orika's - це простий каркас швидкого зіставлення компонентів, оскільки це відбувається за допомогою генерації байтового коду. Він виконує вкладені зіставлення та зіставлення з різними іменами. Детальніше, будь ласка, перевірте тут Зразок відображення може виглядати складно, але для складних сценаріїв це буде просто.

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

Це не робить того, про що задається питання. SerializationUtils.clone()збирається дати новий об’єкт того ж класу. Крім того, він працює лише на серіалізованих класах.
Кірбі,


1

Я вирішив вищезазначену проблему в Котліні, яка мені добре підходить для моєї розробки програм для Android:

 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

Я не хотів додавати залежність до іншого файлу JAR через це, тому написав щось, що відповідало б моїм потребам. Я дотримуюсь домовленості fjorm https://code.google.com/p/fjorm/, що означає, що мої загальнодоступні поля є загальнодоступними, і що я не турбуюся писати сетери та геттери. (на мій погляд, кодом простіше керувати та насправді читати)

Отже, я написав щось (насправді це не набагато складно), що відповідає моїм потребам (передбачається, що у класі є відкритий конструктор без аргументів), і його можна витягти в утилітний клас

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

Антипаттерн: Винайти колесо
Спектакулатіус,

0

Основна ідея Младена спрацювала (спасибі), але потрібні були кілька змін, щоб бути надійними, тому я зробив їх тут.

Єдине місце, де слід використовувати цей тип рішення - це якщо ви хочете клонувати об’єкт, але не можете, оскільки це керований об’єкт. Якщо вам пощастило мати об’єкти, усі з яких мають 100% побічних ефектів для всіх полів, вам слід натомість скористатися опцією BeanUtils.

Тут я використовую утилітні методи lang3 для спрощення коду, тому, якщо ви вставите його, спочатку потрібно імпортувати бібліотеку lang3 Apache.

Скопіюйте код

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>

Ця карта mapper.map має проблеми, оскільки об’єкт не копіює первинні ключі
Мохаммед Рафік,

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

Ми читаємо всі поля класу. Відфільтруйте з результату нестатичні та нефінальні поля. Але може статися помилка доступу до непублічних полів. Наприклад, якщо ця функція знаходиться в тому самому класі, і клас, який копіюється, не містить загальнодоступних полів, трапиться помилка доступу. Рішенням може бути розміщення цієї функції в одному пакеті або зміна доступу до public або в цьому коді всередині поля виклику циклу. SetAccessible (true); що зробить поля доступними


Хоча цей код може запропонувати вирішення питання, краще додати контекст щодо того, чому / як це працює. Це може допомогти майбутнім користувачам навчитися та застосувати ці знання до власного коду. Ви також можете отримати позитивні відгуки користувачів у вигляді голосів "за", коли пояснюється код.
borchvm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.