【问题标题】:Copy all values from fields in one class to another through reflection通过反射将所有值从一个类中的字段复制到另一个
【发布时间】:2010-12-12 16:14:23
【问题描述】:

我的课程基本上是另一个课程的副本。

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

        }
    }

我相信一定有人已经以某种方式做到了这一点

【问题讨论】:

标签: java reflection


【解决方案1】:
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));
        }
    }
}

我们阅读了班级的所有领域。从结果中过滤非静态和非最终字段。 但是访问非公共字段可能会出错。 例如,如果该函数在同一个类中,并且被复制的类不包含公共字段,则会出现访问错误。解决方案可能是将此函数放在同一个包中或更改对公共的访问权限或在循环调用内的此代码中 field.setAccessible (true); 什么将使这些字段可用

【讨论】:

  • 虽然此代码可能会为问题提供解决方案,但最好添加有关其工作原理/方式的上下文。这可以帮助未来的用户学习并将这些知识应用到他们自己的代码中。在解释代码时,您也可能会以赞成票的形式从用户那里获得积极的反馈。
【解决方案2】:
    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 有问题,在实体中它没有复制主键
【解决方案3】:

Mladen 的基本想法奏效了(感谢),但需要进行一些更改才能稳定,所以我在这里贡献了它们。

应该使用这种类型的解决方案的唯一地方是如果您想克隆对象,但因为它是托管对象而不能。如果您有幸拥有所有字段都具有 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;
}

【讨论】:

    【解决方案4】:

    我在 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
    }
    

    }

    【讨论】:

      【解决方案5】:

      这是一个迟到的帖子,但对未来的人们仍然有效。

      Spring 提供了一个实用程序BeanUtils.copyProperties(srcObj, tarObj),当两个类的成员变量名称相同时,它将值从源对象复制到目标对象。

      如果有日期转换,(例如,字符串到日期)“null”将被复制到目标对象。然后,我们可以根据需要显式设置日期的值。

      Apache Common 的 BeanUtils 会在数据类型不匹配时引发错误(尤其是与日期之间的转换)

      希望这会有所帮助!

      【讨论】:

      【解决方案6】:
      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));
             }
        }
        

      【讨论】:

      • 这不是一个可行的解决方案,而是一个很好的起点。需要过滤字段以仅处理两个类中都存在的非静态和公共字段。
      • 这不会忽略父类中的字段吗?
      【解决方案7】:

      这是一个有效且经过测试的解决方案。您可以控制类层次结构中映射的深度。

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

      【讨论】:

      • 我创建了一个类似的解决方案。我将一个类缓存到字段名称到字段映射。
      • 解决方案很好,但你会在i.remove() 这一行遇到问题。即使你已经创建了迭代器,你也不能在Listiterator 上调用remove。应该是ArrayList
      • Farid,remove 不是问题,因为 collectFields() 会创建 ArrayList 对象。
      【解决方案8】:

      你为什么不用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
      • 您仍然需要对源对象和目标对象进行反射,但现在您还引入了二进制/文本/二进制格式和解析开销。
      • 如果使用 Proguard 混淆代码,请注意。如果你使用它,这段代码将不起作用。
      【解决方案9】:

      Spring 有一个内置的BeanUtils.copyProperties 方法。但它不适用于没有 getter/setter 的类。 JSON 序列化/反序列化可以是复制字段的另一种选择。 Jackson 可用于此目的。如果你使用 Spring 在大多数情况下,Jackson 已经在你的依赖列表中。

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

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        【解决方案11】:

        我的解决方案:

        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'自定义方法
        【解决方案12】:

        如果你的依赖中有spring,你也可以使用org.springframework.beans.BeanUtils

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

        【讨论】:

          【解决方案13】:

          Orika 是一个简单快速的 bean 映射框架,因为它通过字节码生成来实现。它执行嵌套映射和具有不同名称的映射。更多详情请check here 示例映射可能看起来很复杂,但对于复杂的场景,它会很简单。

          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() 将提供同一类的新对象。此外,它仅适用于可序列化的类。
          【解决方案14】:

          因此,我不想将依赖项添加到另一个 JAR 文件,所以写了一些适合我需要的东西。我遵循 fjorm https://code.google.com/p/fjorm/ 的约定,这意味着我通常可访问的字段是公共的,并且我不会费心编写 setter 和 getter。 (在我看来,代码实际上更易于管理且更具可读性)

          所以我写了一些适合我需要的东西(实际上并不难)(假设该类具有没有 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;
            }
          

          【讨论】:

          • 反模式:重新发明轮子
          【解决方案15】:

          Dozer

          2012 年 11 月 19 日更新:现在还有 new ModelMapper project

          【讨论】:

            【解决方案16】:

            我想你可以试试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
            

            【讨论】:

              【解决方案17】:

              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 在私有字段上工作得很好,只要 getter/setter 是公共的。关于性能,我没有做任何基准测试,但我相信它会对它自省的 bean 进行一些内部缓存。
              • 这仅在两个 bean 具有相同数据类型的字段时才有效。
              • @To Kra 只有当你有该字段的 getter/setter 时它才会起作用。
              【解决方案18】:

              tooF.set() 的第一个参数应该是目标对象 (too),而不是字段,第二个参数应该是 ,而不是值来自的字段。 (要获取值,您需要调用fromF.get()——再次传入一个目标对象,在本例中为from。)

              大部分反射 API 都是这样工作的。您从类中获得 Field 对象、Method 对象等,而不是从实例中,因此要使用它们(静态除外),您通常需要向它们传递一个实例。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-07-24
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多