developer tip

리플렉션을 통해 한 클래스의 필드에서 다른 클래스로 모든 값 복사

copycodes 2020. 10. 29. 08:17
반응형

리플렉션을 통해 한 클래스의 필드에서 다른 클래스로 모든 값 복사


기본적으로 다른 클래스의 복사 본인 클래스가 있습니다.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

내가 뭐하는 거지하는 클래스의 값을 가하고 ACopyA전송하기 전에 CopyAWeb 서비스 호출을 통해. 이제 기본적으로 class에서 class 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();
            }

        }
    }

왠지 이미이 일을 한 사람이있을 것 같아요


타사 라이브러리를 사용해도 괜찮다면 Apache Commons의 BeanUtilscopyProperties(Object, Object).


gson 라이브러리 https://github.com/google/gson 을 사용하지 않는 이유

클래스 A를 json 문자열로 변환하기 만하면됩니다. 그런 다음 아래 코드를 사용하여 jsonString을 subClass (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;
    }

의 첫 번째 인수 는 필드가 아닌 tooF.set()대상 객체 ( too) 여야하며 두 번째 인수는 값이 제공된 필드가 아니라 이어야합니다 . (값을 얻으려면 fromF.get()-다시 대상 객체를 전달 해야합니다 ( 이 경우) from.)

대부분의 리플렉션 API는 이런 방식으로 작동합니다. 당신은 얻을 Field객체, Method객체 등의 클래스가 아닌 인스턴스에서, 그래서 당신은 일반적으로 그들에게 인스턴스를 전달해야 (정적 제외) 사용할 수 있습니다.


도저

2012 년 11 월 19 일 업데이트 : 이제 새로운 ModelMapper 프로젝트 도 있습니다.


내 솔루션 :

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

다음은 작동하고 테스트 된 솔루션입니다. 클래스 계층 구조에서 매핑의 깊이를 제어 할 수 있습니다.

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

도저 를 사용해 볼 수 있다고 생각합니다 . 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

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

이것은 늦은 게시물이지만 미래의 사람들에게 여전히 효과적 일 수 있습니다.

Spring은 BeanUtils.copyProperties(srcObj, tarObj)두 클래스의 멤버 변수 이름이 같을 때 소스 객체에서 대상 객체로 값을 복사 하는 유틸리티 제공합니다 .

날짜 변환이있는 경우 (예 : 문자열에서 날짜로) 'null'이 대상 개체에 복사됩니다. 그런 다음 필요에 따라 명시 적으로 날짜 값을 설정할 수 있습니다.

BeanUtils from Apache Common은 데이터 유형이 일치하지 않을 때 오류를 발생시킵니다 (특히 날짜와의 변환).

도움이 되었기를 바랍니다!


예 또는 Apache Jakarta의 BeanUtils.


Orika는 바이트 코드 생성을 통해 수행하기 때문에 간단하고 빠른 빈 매핑 프레임 워크입니다. 다른 이름으로 중첩 매핑 및 매핑을 수행합니다. 자세한 내용은 여기에서 확인하십시오. 샘플 매핑은 복잡해 보일 수 있지만 복잡한 시나리오의 경우 간단합니다.

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

의존성에 스프링이 있다면 org.springframework.beans.BeanUtils 를 사용할 수도 있습니다 .

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


내 Android 앱 개발을 위해 잘 작동하는 Kotlin에서 위의 문제를 해결했습니다.

 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
}

}


I didn't want to add dependency to another JAR file because of this, so wrote something which would suit my needs. 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. (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

  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.

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.

Here, I use lang3's utility methods to simplify the code, so if you paste it, you must first import Apache's lang3 library.

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

참고URL : https://stackoverflow.com/questions/1667854/copy-all-values-from-fields-in-one-class-to-another-through-reflection

반응형