Ответ 1
Если вы не возражаете против использования сторонней библиотеки, BeanUtils от Apache Commons справится с этим довольно легко, используя copyProperties(Object, Object)
.
У меня есть класс, который в основном является копией другого класса.
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 Commons справится с этим довольно легко, используя copyProperties(Object, Object)
.
Почему бы вам не использовать библиотеку 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);
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;
}
UPDATE 19 ноября 2012: теперь есть новый проект ModelMapper.
Первый аргумент tooF.set()
должен быть целевым объектом (too
), а не полем, а вторым аргументом должно быть значение, а не поле, из которого исходит значение. (Чтобы получить значение, вам нужно вызвать fromF.get()
- снова передать целевой объект, в данном случае from
.)
Большая часть API отражения работает таким образом. Вы получаете объекты Field
, Method
объекты и т.д. Из класса, а не из экземпляра, поэтому для их использования (кроме статики) вам обычно нужно передать им экземпляр.
Без использования BeanUtils или 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));
}
}
Мое решение:
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;
}
Вот рабочее и протестированное решение. Вы можете контролировать глубину отображения в иерархии классов.
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;
}
}
Я думаю, вы можете попробовать dozer. Он имеет хорошую поддержку преобразования bean в bean. Его также легко использовать. Вы можете ввести его в свое приложение spring или добавить jar в путь класса и его сделать.
Пример вашего случая:
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
Spring имеет встроенный метод BeanUtils.copyProperties
. Но он не работает с классами без getters/seters. Сериализация/десериализация JSON может быть еще одним вариантом для копирования полей. Джексон может быть использован для этой цели. Если вы используете Spring В большинстве случаев Джексон уже находится в вашем списке зависимостей.
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);
Это поздний пост, но он все еще может быть эффективен для людей в будущем.
Spring предоставляет утилиту BeanUtils.copyProperties(srcObj, tarObj)
, которая копирует значения из исходного объекта в целевой объект, когда имена переменных-членов обоих классов совпадают.
Если происходит преобразование даты (например, String to Date), 'null' будет скопирован в целевой объект. Затем мы можем явно установить значения даты в соответствии с требованиями.
BeanUtils из Apache Common
выдает ошибку, когда существует несоответствие типов данных (особенно преобразование в и из даты)
Надеюсь это поможет!
Да или BeanUtils из Apache Jakarta.
Orika - это простая операционная система bean mapping, потому что она работает с генерацией байтового кода. Он содержит вложенные сопоставления и отображения с разными именами. Для более подробной информации, пожалуйста, проверьте здесь Примерное сопоставление может выглядеть сложным, но для сложных сценариев это было бы просто.
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);
Если у вас есть spring в зависимостях, вы также можете использовать org.springframework.beans.BeanUtils.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html
Я решил вышеупомянутую проблему в 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
}
}
Я не хотел добавлять зависимостей к другому 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;
}
Основная идея Младена сработала (спасибо), но для ее исправления потребовалось несколько изменений, поэтому я привел их здесь.
Единственное место, где следует использовать этот тип решения, - это если вы хотите клонировать объект, но не можете, потому что это управляемый объект. Если вам повезло иметь объекты, у всех из которых есть 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;
}
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>