簡體   English   中英

通過反射將一個 class 中字段的所有值復制到另一個

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

我有一個 class,它基本上是另一個 class 的副本。

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

我正在做的是在通過網絡服務調用發送CopyA之前,將 class A中的值放入CopyA中。 現在我想創建一個反射方法,基本上將所有相同的字段(按名稱和類型)從 class A復制到 class 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();
            }

        }
    }

我相信一定有人已經以某種方式做到了這一點

如果您不介意使用第三方庫,Apache Commons 的BeanUtils將使用copyProperties(Object, Object)輕松處理此問題。

為什么不使用 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);

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

這是一個有效且經過測試的解決方案。 您可以控制類層次結構中的映射深度。

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

我的解決方案:

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

這是一個遲到的帖子,但對未來的人仍然有效。

Spring 提供了一個實用程序BeanUtils.copyProperties(srcObj, tarObj) ,當兩個類的成員變量名稱相BeanUtils.copyProperties(srcObj, tarObj)值從源對象復制到目標對象。

如果有日期轉換(例如,字符串到日期)'null' 將被復制到目標對象。 然后,我們可以根據需要明確設置日期的值。

當數據類型不匹配時, Apache Common的 BeanUtils 會拋出錯誤(特別是與 Date 的轉換)

希望這可以幫助!

推土機

2012 年 11 月 19 日更新:現在也有一個新的 ModelMapper 項目

tooF.set()的第一個參數應該是目標對象 ( too ),而不是字段,第二個參數應該是,而不是值來自的字段。 (要獲取該值,您需要調用fromF.get() - 再次傳入目標對象,在本例中為from 。)

大多數反射 API 都是這樣工作的。 您可以從類中而不是從實例中獲取Field對象、 Method對象等,因此要使用它們(靜態除外),您通常需要向它們傳遞一個實例。

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

我想你可以試試 推土機 它對 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
  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)); } }

是的還是來自Apache Jakarta的BeanUtils。

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

如果您在依賴項中有 spring,您還可以使用org.springframework.beans.BeanUtils

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

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

}

這是我的解決方案,將涵蓋子 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);
        }
    }
}

由於這個原因,我不想向另一個 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;
  }

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

我們閱讀了類的所有字段。 從結果中過濾非靜態和非最終字段。 但是訪問非公共字段可能會出錯。 例如,如果這個函數在同一個類中,而被復制的類不包含公共字段,就會出現訪問錯誤。 解決方案可能是將此函數放在同一個包中或將訪問權限更改為 public 或在循環調用 field.setAccessible (true); 的此代碼中; 什么將使字段可用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM