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


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.