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

94

我有一个类,基本上是另一个类的复制。

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

我正在做的是在通过Web服务调用之前,将类A中的值放入CopyA中。现在我想创建一个反射方法,基本上是将与类A相同(按名称和类型)的所有字段从类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();
            }

        }
    }

我相信已经有人以某种方式完成了这个。

2
请参见https://dev59.com/p3M_5IYBdhLWcg3wThR3。 - Ruben Bartelink
使用Apache Jakarta中的BeanUtils。 - Shaun
19个回答

3
我认为您可以尝试使用 Dozer。它对Java 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

2
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() 会返回一个相同类别的新对象。此外,它仅适用于可序列化的类别。 - Kirby

2

1

这是我的解决方案,将涵盖子类情况:

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

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

我们读取类的所有字段。从结果中过滤掉非静态和非终态字段。但是访问非公共字段可能会出现错误。例如,如果此函数在同一类中,并且被复制的类不包含公共字段,则会发生访问错误。解决方案可以是将此函数放置在相同的包中或更改访问权限为public,或者在循环调用内部的代码中使用 field.setAccessible(true); 以使字段可用。

1
虽然这段代码可能提供了问题的解决方案,但最好添加上为什么/如何运作的上下文。这可以帮助未来的用户学习,并将这些知识应用到他们自己的代码中。当代码被解释时,您还可能会得到用户的积极反馈,例如点赞。 - borchvm

1

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

}


0

我不想因此添加另一个JAR文件的依赖,所以写了一些符合我的需求的东西。我遵循fjorm https://code.google.com/p/fjorm/ 的约定,这意味着我通常可以访问的字段是public的,而且我不必费心编写setter和getter方法。(在我看来,代码更易于管理,更易读)

所以我写了一些东西(实际上并不难),它适合我的需求(假设类具有无参数公共构造函数),并且可以提取到实用程序类中。

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

反模式:重复造轮子 - Pwnstar

0

Mladen的基本想法起作用了(谢谢),但需要一些改变才能更加健壮,所以我在这里做出了贡献。

这种类型的解决方案应该仅在您想要克隆对象但无法克隆时使用。如果您有足够幸运的对象,所有字段都有100%无副作用的setter,那么您应该绝对使用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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接