Скопируйте все значения из полей одного класса в другой через отражение


82

У меня есть класс, который по сути является копией другого класса.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

То , что я делаю, ввод значения из класса Aв CopyAперед отправкой CopyAчерез WebService вызова. Теперь я хотел бы создать метод отражения, который в основном копирует все идентичные (по имени и типу) поля из класса 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

Ответы:


102

Если вы не против использования сторонней библиотеки, 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/…
Вадим

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 для строковых представлений, но мы по-прежнему не хотим, чтобы все передавалось в это строковое представление при любой возможности ...
arntg

Обратите внимание, что при использовании отражения веревка является побочным продуктом. Даже через тебя не сохранилось !! Это ответ для новичков в java, и мы стремимся быть кратким и понятным. @arntg
Эрик Хо

Вам по-прежнему понадобится отражение как для исходного, так и для целевого объектов, но теперь вы также вводите двоичное / текстовое / двоичное форматирование и накладные расходы на синтаксический анализ.
Evvo

Обратите внимание, если вы используете Proguard для обфускации кода. Если вы его используете, этот код работать не будет.
SebastiaoRealino

8

BeanUtils копирует только общедоступные поля и работает немного медленно. Вместо этого используйте методы получения и установки.

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 отлично работает с частными полями, если геттеры / сеттеры являются общедоступными. Что касается производительности, я не проводил никаких тестов, но я считаю, что он выполняет некоторое внутреннее кеширование bean-компонентов, которые он изучил.
Грег Кейс,

2
это будет работать только в том случае, если два bean-компонента имеют одинаковый тип данных полей.
TimeToCodeTheRoad

@To Kra Это будет работать, только если у вас есть геттер / сеттер для этого поля.
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
Фарид

Фарид, удаление не может быть проблемой, потому что 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
Мохсен Каши,

4

Первым аргументом tooF.set()должен быть целевой объект ( too), а не поле, а вторым аргументом должно быть значение , а не поле, из которого оно получено. (Чтобы получить значение, вам нужно вызвать fromF.get()- в данном случае снова передавая целевой объект from.)

Так работает большая часть API отражения. Вы получаете Fieldобъекты, Methodобъекты и т. Д. Из класса, а не из экземпляра, поэтому для их использования (кроме статики) вам обычно необходимо передать им экземпляр.



4

Это запоздалый пост, но он все еще может быть эффективен для людей в будущем.

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

Если есть преобразование даты (например, строка в дату) 'null' будет скопировано в целевой объект. Затем мы можем явно установить значения даты по мере необходимости.

BeanUtils from Apache Commonвыдает ошибку при несоответствии типов данных (особенно преобразование в дату и обратно)

Надеюсь это поможет!


Это не дает никакой дополнительной информации, кроме принятого ответа stackoverflow.com/a/1667911/4589003
Судип Бхандари,

3

Думаю, можно попробовать бульдозер . Он имеет хорошую поддержку преобразования bean-компонентов. Его также легко использовать. Вы можете либо ввести его в свое приложение Spring, либо добавить банку в путь к классу, и все готово.

Для примера вашего случая:

 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 02

2

Spring имеет встроенный 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 - это простая более быстрая структура сопоставления bean-компонентов, потому что она использует генерацию байтового кода. Он выполняет вложенные сопоставления и сопоставления с разными именами. Дополнительные сведения см. Здесь. Пример сопоставления может показаться сложным, но для сложных сценариев это будет просто.

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()собирается предоставить новый объект того же класса. Кроме того, он работает только с сериализуемыми классами.
Kirby


1

Я решил вышеуказанную проблему в Kotlin, которая отлично подходит для моей разработки приложений для 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 для упрощения кода, поэтому, если вы вставляете его, вы должны сначала импортировать библиотеку Apache lang3.

Скопировать код

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

Читаем все поля класса. Отфильтруйте нестатические и незавершенные поля из результата. Но может быть ошибка доступа к закрытым полям. Например, если эта функция находится в том же классе, а копируемый класс не содержит общедоступных полей, произойдет ошибка доступа. Решением может быть размещение этой функции в том же пакете или изменение доступа к общедоступному или в этом коде внутри поля вызова цикла. SetAccessible (true); что сделает поля доступными


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