93

I have a class which is basically a copy of another class.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

What I am doing is putting values from class A into CopyA before sending CopyA through a webservice call. Now I would like to create a reflection-method that basically copies all fields that are identical (by name and type) from class A to class CopyA.

How can I do this?

This is what I have so far, but it doesn't quite work. I think the problem here is that I am trying to set a field on the field I am looping through.

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

        }
    }

I am sure there must be someone that has already done this somehow

Sastrija
  • 3,284
  • 6
  • 47
  • 64
Shervin Asgari
  • 23,901
  • 30
  • 103
  • 143

19 Answers19

117

If you don't mind using a third party library, BeanUtils from Apache Commons will handle this quite easily, using copyProperties(Object, Object).

Colourfit
  • 375
  • 3
  • 19
Greg Case
  • 3,200
  • 1
  • 19
  • 17
  • 15
    Apparently BeanUtils doesn't work with null Date fields. Use Apache PropertyUtils if this is a problem for you: http://www.mail-archive.com/user@commons.apache.org/msg02246.html – ripper234 Nov 28 '11 at 13:51
  • 11
    This apparently does not work for private fields with no getter and setters. Any solution that works on directly with fields, rather than properties? – Andrea Ratto Oct 16 '15 at 17:13
  • It neither works with plain public fields without getters: https://stackoverflow.com/questions/34263122/copy-object-properties-by-direct-field-access – Vadzim Mar 14 '19 at 22:18
20

Why don't you use gson library https://github.com/google/gson

you just convert the Class A to json string. Then convert jsonString to you subClass (CopyA) .using below code:

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);
Giru Bhai
  • 14,370
  • 5
  • 46
  • 74
Eric Ho
  • 395
  • 2
  • 4
  • 1
    Why generate another String which could also be large? There are better alternatives described as answers here. At least we (the industry) progressed from XML to json for string representations, but we still don't want everything passed to that string representation at any given chance ... – arntg Jan 29 '19 at 02:00
  • 1
    Please noted that the string is a side product when using reflection . Even through you did not save it !! This is a answer for java beginners, and aim to be in short and clean way. @arntg – Eric Ho Feb 03 '19 at 06:17
  • 1
    You'd still need reflection on both the source and destination objects, but now you're also introducing binary/text/binary formatting and parsing overhead. – Evvo Oct 09 '19 at 17:08
  • 1
    Pay attention if you use Proguard to obfuscate the code. If you use it, this code will not work. – SebaReal Jun 03 '20 at 14:21
  • this is useful in my case thanks – Mugeesh Husain Nov 25 '22 at 01:27
11

BeanUtils will only copy public fields and is a bit slow. Instead go with getter and setter methods.

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;
    }
olyanren
  • 1,448
  • 4
  • 24
  • 42
Supun Sameera
  • 2,683
  • 3
  • 17
  • 14
  • BeanUtils works just fine on private fields, as long as the getters/setters are public. Regarding performance, I haven't done any benchmarking, but I believe it does some internal caching of the bean's it has introspected. – Greg Case Dec 14 '11 at 21:18
  • 2
    this will only work if the two beans have the same data type of fields. – TimeToCodeTheRoad Jun 26 '12 at 21:28
  • @To Kra It would work only if you have getter/setter for that field. – WoLfPwNeR Jan 09 '17 at 23:58
10

Here is a working and tested solution. You can control the depth of the mapping in the class hierarchy.

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;
    }
}
JHead
  • 356
  • 2
  • 12
  • 1
    I have created a similar solution. I cached a class to field names to field map. – Orden Aug 09 '18 at 20:36
  • The solution is nice but you're gonna have a problem in this line `i.remove()`. Even if you've created iterator you can't call `remove` on `List`'s `iterator`. It should be `ArrayList` – Farid Jul 04 '20 at 23:13
  • Farid, remove can't be a problem because collectFields() creates ArrayList objects. – JHead Jul 06 '20 at 08:01
8

Spring has a built in BeanUtils.copyProperties method. But it doesn't work with classes without getters/setters. JSON serialization/deserialization can be another option for copying fields. Jackson can be used for this purpose. If you are using Spring In most cases Jackson is already in your dependency list.

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);
Fırat Küçük
  • 5,613
  • 2
  • 50
  • 53
6

My solution:

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;
}
18446744073709551615
  • 16,368
  • 4
  • 94
  • 127
Nik Kashi
  • 4,447
  • 3
  • 40
  • 63
6

This is a late post, but can still be effective for people in future.

Spring provides a utility BeanUtils.copyProperties(srcObj, tarObj) which copies values from source object to target object when the names of the member variables of both classes are the same.

If there is a date conversion, (eg, String to Date) 'null' would be copied to the target object. We can then, explicitly set the values of the date as required.

The BeanUtils from Apache Common throws an error when there is a mismatch of data-types (esp. conversion to and from Date)

Hope this helps!

Nicholas K
  • 15,148
  • 7
  • 31
  • 57
5

The first argument to tooF.set() should be the target object (too), not the field, and the second argument should be the value, not the field the value comes from. (To get the value, you need to call fromF.get() -- again passing in a target object, in this case from.)

Most of the reflection API works this way. You get Field objects, Method objects, and so on from the class, not from an instance, so to use them (except for statics) you generally need to pass them an instance.

David Moles
  • 48,006
  • 27
  • 136
  • 235
4

Dozer

UPDATE Nov 19 2012: There's now a new ModelMapper project too.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
3

I think you can try dozer. It has good support for bean to bean conversion. Its also easy to use. You can either inject it into your spring application or add the jar in class path and its done.

For an example of your case :

 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
Priyank Doshi
  • 12,895
  • 18
  • 59
  • 82
3
  1. Without using BeanUtils or 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));
         }
    }
    
Jing Li
  • 14,547
  • 7
  • 57
  • 69
  • This is not a working solution but a good starting point. The fields need to be filtered to handle only the non-static and public fields that are present in both of the classes. – JHead Aug 30 '17 at 07:25
  • Wouldn't this ignore fields in parent classes? – Evvo Jan 02 '20 at 19:38
2

Orika's is simple faster bean mapping framework because it does through byte code generation. It does nested mappings and mappings with different names. For more details, please check here Sample mapping may look complex, but for complex scenarios it would be simple.

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);
Nagappan
  • 346
  • 2
  • 8
  • This does not do what the question is asking for. `SerializationUtils.clone()` is going to give a new object of the same class. Additionally it only works on serializable classes. – Kirby Jul 29 '15 at 23:24
2

If you have spring in the list of dependencies you can use org.springframework.beans.BeanUtils.

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

db80
  • 4,157
  • 1
  • 38
  • 38
1

I solved the above problem in Kotlin that works fine for me for my Android Apps Development:

 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
}

}

Babul Mirdha
  • 3,816
  • 1
  • 22
  • 25
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));
        }
    }
}

We read all the fields of the class. Filter non-static and non-final fields from the result. But there may be an error accessing non-public fields. For example, if this function is in the same class, and the class being copied does not contain public fields, an access error will occur. The solution may be to place this function in the same package or change access to public or in this code inside the loop call field.setAccessible (true); what will make the fields available

Zedong
  • 71
  • 1
  • 5
  • 1
    While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained. – borchvm Mar 19 '20 at 07:00
1

Here is my solution, will cover the child class case:

/**
 * 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);
        }
    }
}
Elikill58
  • 4,050
  • 24
  • 23
  • 45
Ejaz
  • 31
  • 4
0

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 Adamovic
  • 3,071
  • 2
  • 29
  • 44
0

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;
}
Emily Crutcher
  • 638
  • 5
  • 10
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>
Ran Adler
  • 3,587
  • 30
  • 27