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


82

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

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 Jakarta의 BeanUtils.
Shaun F

답변:


102

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


13
분명히 BeanUtils는 null Date 필드에서 작동하지 않습니다. 이것이 문제라면 Apache PropertyUtils를 사용하십시오 : mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
이것은 getter와 setter가없는 private 필드에서는 작동하지 않는 것 같습니다. 속성이 아닌 필드에서 직접 작동하는 솔루션이 있습니까?
Andrea Ratto 2015 년

게터없이 일반 대중 필드 그것은 어느 작품 : stackoverflow.com/questions/34263122/...
바드

17

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

왜 클 수있는 또 다른 문자열을 생성합니까? 여기에 답변으로 설명 된 더 나은 대안이 있습니다. 적어도 우리 (업계)는 문자열 표현을 위해 XML에서 json으로 발전했지만 여전히 모든 것이 주어진 기회에 해당 문자열 표현으로 전달되는 것을 원하지는 않습니다 ...
arntg

반사를 사용할 때 현은 부수 품이라는 점에 유의하십시오. 당신을 통해서도 그것을 저장하지 않았습니다 !! 이것은 자바 초보자를위한 답변이며 짧고 깨끗한 방법을 목표로합니다. @arntg
에릭 호

원본 및 대상 개체 모두에 대한 반영이 여전히 필요하지만 이제 바이너리 / 텍스트 / 이진 형식화 및 파싱 오버 헤드도 도입하고 있습니다.
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;
    }

BeanUtils는 getter / setter가 공용 인 한 개인 필드에서 잘 작동합니다. 성능과 관련하여 벤치마킹을 수행하지 않았지만 내부 검사 한 Bean의 내부 캐싱을 수행한다고 생각합니다.
Greg Case

2
이것은 두 개의 빈이 동일한 데이터 유형의 필드를 갖는 경우에만 작동합니다.
TimeToCodeTheRoad

@To Kra 해당 필드에 대한 getter / setter가있는 경우에만 작동합니다.
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
비슷한 솔루션을 만들었습니다. 필드 맵에 필드 이름에 클래스를 캐시했습니다.
Orden

해결책은 좋지만이 줄에 문제가있을 것 i.remove()입니다. 당신이 반복자를 생성 한 경우에도 당신은 호출 할 수 없습니다 removeList'들 iterator. 그것은해야ArrayList
파리 드

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) 여야하며 두 번째 인수는 값이 제공되는 필드가 아니라 이어야합니다 . (값을 얻으려면 fromF.get()-다시 대상 객체를 전달 해야합니다 ( 이 경우) from.)

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



4

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

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

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

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

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


이것은 허용되는 것보다 추가 정보를 제공하지 않습니다. stackoverflow.com/a/1667911/4589003 답변
Sudip Bhandari

3

도저 를 사용해 볼 수 있다고 생각합니다 . Bean에서 Bean으로의 변환을 잘 지원합니다. 또한 사용하기 쉽습니다. 스프링 애플리케이션에 주입하거나 클래스 경로에 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 직렬화 / 역 직렬화는 필드 복사를위한 또 다른 옵션이 될 수 있습니다. 이 목적으로 Jackson을 사용할 수 있습니다. Spring을 사용하는 경우 대부분의 경우 Jackson은 이미 종속성 목록에 있습니다.

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

1

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

이것은 질문이 요구하는 바를 수행하지 않습니다. SerializationUtils.clone()같은 클래스의 새로운 객체를 줄 것입니다. 또한 직렬화 가능한 클래스에서만 작동합니다.
Kirby


1

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

}


0

이 때문에 다른 JAR 파일에 종속성을 추가하고 싶지 않았기 때문에 내 필요에 맞는 것을 작성했습니다. fjorm https://code.google.com/p/fjorm/ 의 규칙을 따릅니다. 즉, 일반적으로 액세스 할 수있는 필드가 공개되어 있고 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;
  }

반 패턴 : 바퀴 재발견
Spektakulatius

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

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

우리는 수업의 모든 분야를 읽었습니다. 결과에서 비 정적 및 최종 필드를 필터링합니다. 그러나 비공개 필드에 액세스하는 데 오류가있을 수 있습니다. 예를 들어이 함수가 동일한 클래스에 있고 복사중인 클래스에 공용 필드가 포함되어 있지 않으면 액세스 오류가 발생합니다. 해결책은이 함수를 동일한 패키지에 배치하거나 public 또는이 코드의 루프 호출 field.setAccessible (true)에 대한 액세스를 변경하는 것입니다. 필드를 사용할 수있게 만드는 것


이 코드는 질문에 대한 해결책을 제공 할 수 있지만 작동 이유 / 방법에 대한 컨텍스트를 추가하는 것이 좋습니다. 이것은 미래의 사용자가 그 지식을 배우고 자신의 코드에 적용하는 데 도움이 될 수 있습니다. 또한 코드가 설명 될 때 사용자로부터 찬성 투표 형태로 긍정적 인 피드백을받을 가능성이 있습니다.
borchvm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.