简体   繁体   English

通过反射将一个 class 中字段的所有值复制到另一个

[英]Copy all values from fields in one class to another through reflection

I have a class which is basically a copy of another class.我有一个 class,它基本上是另一个 class 的副本。

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

What I am doing is putting values from class A into CopyA before sending CopyA through a webservice call.我正在做的是在通过网络服务调用发送CopyA之前,将 class A中的值放入CopyA中。 Now I would like to create a reflection-method that basically copies all fields that are identical (by name and type) from class A to class CopyA .现在我想创建一个反射方法,基本上将所有相同的字段(按名称和类型)从 class A复制到 class CopyA

How can I do this?我怎样才能做到这一点?

This is what I have so far, but it doesn't quite work.这是我到目前为止所拥有的,但它并不完全有效。 I think the problem here is that I am trying to set a field on the field I am looping through.我认为这里的问题是我试图在我正在循环的字段上设置一个字段。

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

        }
    }

I am sure there must be someone that has already done this somehow我相信一定有人已经以某种方式做到了这一点

如果您不介意使用第三方库,Apache Commons 的BeanUtils将使用copyProperties(Object, Object)轻松处理此问题。

Why don't you use gson library https://github.com/google/gson为什么不使用 gson 库https://github.com/google/gson

you just convert the Class A to json string.您只需将 A 类转换为 json 字符串。 Then convert jsonString to you subClass (CopyA) .using below code:然后将 jsonString 转换为您的子类(CopyA)。使用以下代码:

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

BeanUtils will only copy public fields and is a bit slow. BeanUtils 只会复制公共字段,速度有点慢。 Instead go with getter and setter methods.而是使用 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;
    }

Here is a working and tested solution.这是一个有效且经过测试的解决方案。 You can control the depth of the mapping in the class hierarchy.您可以控制类层次结构中的映射深度。

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

My solution:我的解决方案:

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

This is a late post, but can still be effective for people in future.这是一个迟到的帖子,但对未来的人仍然有效。

Spring provides a utility BeanUtils.copyProperties(srcObj, tarObj) which copies values from source object to target object when the names of the member variables of both classes are the same. Spring 提供了一个实用程序BeanUtils.copyProperties(srcObj, tarObj) ,当两个类的成员变量名称相BeanUtils.copyProperties(srcObj, tarObj)值从源对象复制到目标对象。

If there is a date conversion, (eg, String to Date) 'null' would be copied to the target object.如果有日期转换(例如,字符串到日期)'null' 将被复制到目标对象。 We can then, explicitly set the values of the date as required.然后,我们可以根据需要明确设置日期的值。

The BeanUtils from Apache Common throws an error when there is a mismatch of data-types (esp. conversion to and from Date)当数据类型不匹配时, Apache Common的 BeanUtils 会抛出错误(特别是与 Date 的转换)

Hope this helps!希望这可以帮助!

Dozer推土机

UPDATE Nov 19 2012: There's now a new ModelMapper project too. 2012 年 11 月 19 日更新:现在也有一个新的 ModelMapper 项目

The first argument to tooF.set() should be the target object ( too ), not the field, and the second argument should be the value , not the field the value comes from. tooF.set()的第一个参数应该是目标对象 ( too ),而不是字段,第二个参数应该是,而不是值来自的字段。 (To get the value, you need to call fromF.get() -- again passing in a target object, in this case from .) (要获取该值,您需要调用fromF.get() - 再次传入目标对象,在本例中为from 。)

Most of the reflection API works this way.大多数反射 API 都是这样工作的。 You get Field objects, Method objects, and so on from the class, not from an instance, so to use them (except for statics) you generally need to pass them an instance.您可以从类中而不是从实例中获取Field对象、 Method对象等,因此要使用它们(静态除外),您通常需要向它们传递一个实例。

Spring has a built in BeanUtils.copyProperties method. Spring 有一个内置的BeanUtils.copyProperties方法。 But it doesn't work with classes without getters/setters.但它不适用于没有 getter/setter 的类。 JSON serialization/deserialization can be another option for copying fields. JSON 序列化/反序列化可以是复制字段的另一种选择。 Jackson can be used for this purpose. Jackson 可以用于此目的。 If you are using Spring In most cases Jackson is already in your dependency list.如果您使用的是 Spring 在大多数情况下 Jackson 已经在您的依赖项列表中。

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

I think you can try dozer .我想你可以试试 推土机 It has good support for bean to bean conversion.它对 bean 到 bean 的转换有很好的支持。 Its also easy to use.它也很容易使用。 You can either inject it into your spring application or add the jar in class path and its done.您可以将它注入到您的 spring 应用程序中,也可以将 jar 添加到类路径中并完成。

For an example of your case :以您的情况为例:

 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
  1. Without using BeanUtils or Apache Commons不使用 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)); } }

是的还是来自Apache Jakarta的BeanUtils。

Orika's is simple faster bean mapping framework because it does through byte code generation. Orika 是一个简单的快速 bean 映射框架,因为它通过字节码生成来完成。 It does nested mappings and mappings with different names.它执行嵌套映射和具有不同名称的映射。 For more details, please check here Sample mapping may look complex, but for complex scenarios it would be 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);

If you have spring in the dependencies you can also use org.springframework.beans.BeanUtils .如果您在依赖项中有 spring,您还可以使用org.springframework.beans.BeanUtils

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html

I solved the above problem in Kotlin that works fine for me for my Android Apps Development:我在 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
}

} }

Here is my solution, will cover the child class case:这是我的解决方案,将涵盖子 class 案例:

/**
 * This methods transfer the attributes from one class to another class if it
 * has null values.
 * 
 * @param fromClass from class
 * @param toClass   to class
 */
private void loadProperties(Object fromClass, Object toClass) {
    if (Objects.isNull(fromClass) || Objects.isNull(toClass))
        return;
    
    Field[] fields = toClass.getClass().getDeclaredFields();
    Field[] fieldsSuperClass = toClass.getClass().getSuperclass().getDeclaredFields();
    Field[] fieldsFinal = new Field[fields.length + fieldsSuperClass.length];

    Arrays.setAll(fieldsFinal, i -> (i < fields.length ? fields[i] : fieldsSuperClass[i - fields.length]));

    for (Field field : fieldsFinal) {
        field.setAccessible(true);
        try {
            String propertyKey = field.getName();
            if (field.get(toClass) == null) {
                Field defaultPropertyField = fromClass.getClass().getDeclaredField(propertyKey);
                defaultPropertyField.setAccessible(true);
                Object propertyValue = defaultPropertyField.get(fromClass);
                if (propertyValue != null)
                    field.set(toClass, propertyValue);
            }
        } catch (IllegalAccessException e) {
            logger.error(() -> "Error while loading properties from " + fromClass.getClass() +" and to " +toClass.getClass(), e);
        } catch (NoSuchFieldException e) {
            logger.error(() -> "Exception occurred while loading properties from " + fromClass.getClass()+" and to " +toClass.getClass(), e);
        }
    }
}

I didn't want to add dependency to another JAR file because of this, so wrote something which would suit my needs.由于这个原因,我不想向另一个 JAR 文件添加依赖项,所以写了一些适合我需要的东西。 I follow the convention of fjorm https://code.google.com/p/fjorm/ which means that my generally accessible fields are public and that I don't bother to write setters and getters.我遵循 fjorm https://code.google.com/p/fjorm/的约定,这意味着我通常可访问的字段是公开的,而且我不会费心编写 setter 和 getter。 (in my opinion code is easier to manage and more readable actually) (在我看来,代码实际上更易于管理且更具可读性)

So I wrote something (it's not actually much difficult) which suits my needs (assumes that the class has public constructor without args) and it could be extracted into utility class所以我写了一些适合我需要的东西(实际上并不难)(假设该类具有没有 args 的公共构造函数)并且它可以被提取到实用程序类中

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

Mladen's basic idea worked (thanks), but needed a few changes to be robust, so I contributed them here. Mladen 的基本想法奏效了(谢谢),但需要一些更改才能保持稳健,所以我在这里贡献了它们。

The only place where this type of solution should be used is if you want to clone the object, but can't because it is a managed object.应该使用这种类型的解决方案的唯一地方是如果您想克隆对象,但不能,因为它是托管对象。 If you are lucky enough to have objects that all have 100% side-effect free setters for all fields, you should definitely use the BeanUtils option instead.如果您有幸拥有所有字段都具有 100% 无副作用的 setter 的对象,那么您绝对应该改用 BeanUtils 选项。

Here, I use lang3's utility methods to simplify the code, so if you paste it, you must first import Apache's lang3 library.在这里,我使用lang3的实用方法来简化代码,所以如果你粘贴它,你必须先导入Apache的lang3库。

Copy code复制代码

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

We read all the fields of the class.我们阅读了类的所有字段。 Filter non-static and non-final fields from the result.从结果中过滤非静态和非最终字段。 But there may be an error accessing non-public fields.但是访问非公共字段可能会出错。 For example, if this function is in the same class, and the class being copied does not contain public fields, an access error will occur.例如,如果这个函数在同一个类中,而被复制的类不包含公共字段,就会出现访问错误。 The solution may be to place this function in the same package or change access to public or in this code inside the loop call field.setAccessible (true);解决方案可能是将此函数放在同一个包中或将访问权限更改为 public 或在循环调用 field.setAccessible (true); 的此代码中; what will make the fields available什么将使字段可用

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将所有值从一个JSONObject复制到另一个? - How to copy all the values from one JSONObject to another? 反射将非null属性从一个对象复制到另一个BeanUtils - reflection copy non null properties from one object to another BeanUtils 如何将一个对象字段数据复制到同一类的另一对象? - How it copy one object fields data to another object of the same class? 如何将值从一个Java类复制到具有相同属性的另一个类 - How to copy the values from one Java class to another class with same properties 获取非实例化类的字段值 - 反射 - Get fields values of non instantiable class - Reflection 将所有字段从一个对象移动到另一个对象 - Moving all fields from one object into another 如果所有属性的值不为 null 或为空,如何将所有属性的值从对象的一个​​实例复制到另一个实例 - How to copy values of all attributes from one instance of an object to another if they are not null or empty 如何将字段从一个 object 复制到另一个,包括 collections - How to copy fields from one object to another including collections 如何从使用反射实现接口的类的字段中获取值? - How do i get a values from fields of a class which implements interface using reflection? 将大多数属性从一个 class object 复制到另一个 class - Copy most attributes from one class object to another class
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM