Tengo una clase que es básicamente una copia de otra clase.
public class A {
int a;
String b;
}
public class CopyA {
int a;
String b;
}
Lo que estoy haciendo es poner los valores de la clase A
en CopyA
antes de enviarlos CopyA
a través de una llamada de servicio web. Ahora me gustaría crear un método de reflexión que básicamente copia todos los campos que son idénticos (por nombre y tipo) de una clase A
a otra CopyA
.
¿Cómo puedo hacer esto?
Esto es lo que tengo hasta ahora, pero no funciona del todo. Creo que el problema aquí es que estoy tratando de establecer un campo en el campo que estoy recorriendo.
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();
}
}
}
Estoy seguro de que debe haber alguien que ya haya hecho esto de alguna manera.
java
reflection
Shervin Asgari
fuente
fuente
Respuestas:
Si no le importa usar una biblioteca de terceros, BeanUtils de Apache Commons lo manejará con bastante facilidad, usando
copyProperties(Object, Object)
.fuente
¿Por qué no utiliza la biblioteca gson https://github.com/google/gson
simplemente convierta la cadena Class A a json. Luego convierta jsonString a su subclase (CopyA). Usando el siguiente código:
Gson gson= new Gson(); String tmp = gson.toJson(a); CopyA myObject = gson.fromJson(tmp,CopyA.class);
fuente
BeanUtils solo copiará campos públicos y es un poco lento. En su lugar, utilice los métodos getter y 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; }
fuente
Aquí hay una solución probada y funcional. Puede controlar la profundidad del mapeo en la jerarquía de clases.
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; } }
fuente
i.remove()
. Incluso si ha creado iterador no se puede llamarremove
deList
'siterator
. Debería serArrayList
Mi solución:
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; }
fuente
El primer argumento
tooF.set()
debe ser el objeto de destino (too
), no el campo, y el segundo argumento debe ser el valor , no el campo del que proviene el valor. (Para obtener el valor, debe llamarfromF.get()
, nuevamente pasando un objeto de destino, en este casofrom
).La mayor parte de la API de reflexión funciona de esta manera. Obtienes
Field
objetos,Method
objetos, etc. de la clase, no de una instancia, por lo que para usarlos (excepto para estática) generalmente necesitas pasarles una instancia.fuente
Topadora
ACTUALIZACIÓN 19 de noviembre de 2012: Ahora también hay un nuevo proyecto ModelMapper .
fuente
Esta es una publicación tardía, pero aún puede ser efectiva para las personas en el futuro.
Spring proporciona una utilidad
BeanUtils.copyProperties(srcObj, tarObj)
que copia valores del objeto de origen al objeto de destino cuando los nombres de las variables miembro de ambas clases son iguales.Si hay una conversión de fecha, (p. Ej., Cadena a fecha) se copiará "nulo" en el objeto de destino. Entonces podemos establecer explícitamente los valores de la fecha según sea necesario.
El BeanUtils de
Apache Common
arroja un error cuando no coinciden los tipos de datos (especialmente la conversión desde y hacia la fecha)¡Espero que esto ayude!
fuente
Creo que puedes probar la topadora . Tiene un buen soporte para la conversión de frijol a frijol. También es fácil de usar. Puede inyectarlo en su aplicación de primavera o agregar el jar en la ruta de clases y listo.
Para un ejemplo de su caso:
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
fuente
Sin usar BeanUtils o 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)); } }
fuente
Spring tiene un
BeanUtils.copyProperties
método incorporado . Pero no funciona con clases sin getters / setters. La serialización / deserialización de JSON puede ser otra opción para copiar campos. Jackson se puede utilizar para este propósito. Si está utilizando Spring En la mayoría de los casos, Jackson ya está en su lista de dependencias.ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Clazz copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);
fuente
Orika es un marco de mapeo de beans más rápido y simple porque lo hace mediante la generación de código de bytes. Hace asignaciones anidadas y asignaciones con diferentes nombres. Para obtener más detalles, consulte aquí El mapeo de muestra puede parecer complejo, pero para escenarios complejos sería simple.
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);
fuente
SerializationUtils.clone()
va a dar un nuevo objeto de la misma clase. Además, solo funciona en clases serializables.Si tiene spring en las dependencias, también puede usar org.springframework.beans.BeanUtils .
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html
fuente
Resolví el problema anterior en Kotlin que funciona bien para mí para mi desarrollo de aplicaciones de 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 }
}
fuente
No quería agregar dependencia a otro archivo JAR debido a esto, así que escribí algo que se adaptara a mis necesidades. Sigo la convención de fjorm https://code.google.com/p/fjorm/ lo que significa que mis campos generalmente accesibles son públicos y que no me molesto en escribir setters y getters. (en mi opinión, el código es más fácil de administrar y más legible en realidad)
Entonces escribí algo (en realidad no es muy difícil) que se adapta a mis necesidades (supone que la clase tiene un constructor público sin argumentos) y podría extraerse en la clase de utilidad
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; }
fuente
La idea básica de Mladen funcionó (gracias), pero necesitaba algunos cambios para ser sólida, así que los aporté aquí.
El único lugar donde se debe usar este tipo de solución es si desea clonar el objeto, pero no puede hacerlo porque es un objeto administrado. Si tiene la suerte de tener objetos que tengan un 100% libre de efectos secundarios para todos los campos, definitivamente debería usar la opción BeanUtils.
Aquí, utilizo los métodos de utilidad de lang3 para simplificar el código, por lo que si lo pega, primero debe importar la biblioteca lang3 de Apache.
Copiar codigo
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; }
fuente
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>
fuente
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)); } } }
Leemos todos los campos de la clase. Filtra los campos no estáticos y no finales del resultado. Pero puede haber un error al acceder a campos no públicos. Por ejemplo, si esta función está en la misma clase y la clase que se copia no contiene campos públicos, se producirá un error de acceso. La solución puede ser colocar esta función en el mismo paquete o cambiar el acceso a público o en este código dentro del campo de llamada al bucle .setAccessible (true); qué hará que los campos estén disponibles
fuente