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


82

我有一堂课,基本上是另一堂课的副本。

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

我正在做的是在通过Web服务调用发送之前,将类中的值A放入。现在我想以创建反射方法,其基本上从类副本是相同的(按名称和类型)的所有字段类。CopyACopyAACopyA

我怎样才能做到这一点?

到目前为止,这是我所拥有的,但效果不佳。我认为这里的问题是我试图在要遍历的字段上设置一个字段。

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

        }
    }

我敢肯定一定有人已经这样做了



是的,或者来自Apache Jakarta的BeanUtils。
Shaun F

Answers:


102

如果您不介意使用第三方库,则Apache Commons的BeanUtils可以使用来轻松处理copyProperties(Object, Object)


13
显然,BeanUtils不适用于null日期字段。如果您遇到
ripper234

10
这显然不适用于没有getter和setter的私有字段。有什么解决方案可直接用于字段而不是属性?
Andrea Ratto

它既
不适用于

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

为什么生成另一个可能也很大的String?这里有更好的替代方法作为答案。至少我们(该行业)从XML到json都已使用json进行了字符串表示,但是我们仍然不希望一切在任何给定的机会下传递给该字符串表示形式……
arntg

请注意,使用反射时,弦线是副产品。即使通过你也没有保存!这是Java初学者的答案,其目标是简洁明了。@arntg
Eric Ho

您仍然需要在源对象和目标对象上进行反思,但是现在您还引入了二进制/文本/二进制格式和解析开销。
Evvo

如果您使用Proguard混淆代码,请注意。如果使用它,则此代码将不起作用。
SebastiaoRealino

8

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

只要getter / setter是公开的,BeanUtils便可以在私有领域正常工作。关于性能,我还没有做过任何基准测试,但是我相信它可以对自省的bean进行内部缓存。
格雷格案

2
仅当两个bean的字段数据类型相同时,此方法才有效。
TimeToCodeTheRoad 2012年

@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()。即使你已经创建迭代器不能调用removeListiterator。应该是ArrayList
Farid

Farid,remove不会有问题,因为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;
}

我认为这不适用于自定义对象。只有当你有没有父母,只有原始的领域一类
Shervin阿斯卡里

为了覆盖超类字段,我使用了“ getAllModelFields”自定义方法
Mohsen Kashi

4

的第一个参数tooF.set()应该是目标对象(too),而不是字段,第二个参数应该是value,而不是该值来自的字段。(要获取值,您需要调用fromF.get()-在这种情况下,再次传入目标对象from。)

大多数反射API都是以这种方式工作的。您是从类而不是从实例获取Field对象,Method对象等,因此要使用它们(除了静态对象),通常需要向它们传递一个实例。



4

这是一个较晚的帖子,但将来对人们仍然有效。

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

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

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

希望这可以帮助!


除了接受的stackoverflow.com/a/1667911/4589003答案,此信息不提供任何其他信息
Sudip Bhandari'Bugari'18

3

我想你可以试试推土机。它对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

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

2

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

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()将提供一个相同类别的新对象。另外,它仅适用于可序列化的类。
柯比


1

我在Kotlin中解决了上述问题,该问题对我的Android Apps开发非常适用:

 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/的约定,这意味着我通常可以访问的字段是公开的,并且我不会打扰写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;
  }

反模式:重新发明轮子
Spektakulatius

0

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

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存在问题,在实体中它没有复制主键
Mohammed Rafeeq

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.