8

I have two java objects as follows:

class A {
  int a;
  int b;
}

class B {
  int a;
  Double b;
}

A objA = new A();
objA.a = 5;
objA.b = 6;

I want to clone objA into objB such that field b gets converted to Double when accessed from objB i.e.

objB.b = 6.0
objB.a = 5

Note:

  • Classes have to different.
  • Classes are so huge that individual copying and typecasting does not seem like a very good option.
  • I can't extend class B from A because the field names are exactly the same except for few fields which have their data types changed from int to Double in class B.
Abhinav
  • 514
  • 1
  • 7
  • 17
  • There are lots of [possible methods](https://stackoverflow.com/questions/1106102/clone-vs-copy-constructor-vs-factory-method) to archieve that. – OH GOD SPIDERS Jul 19 '18 at 14:00
  • I would probably go with a copy constructor in one/both of the classes. You don't really have an option about casting from integer to double AFAIK. – Tim Biegeleisen Jul 19 '18 at 14:01
  • Looking at "_changing data types_" you want to have a look at this question: [I need to convert an int variable to double](https://stackoverflow.com/questions/13252903/i-need-to-convert-an-int-variable-to-double) – LuCio Jul 19 '18 at 14:02
  • There are libraries to do that. Check ALL the answers of: https://stackoverflow.com/questions/5937567/copy-pojo-content-from-one-bean-to-another – sanastasiadis Jul 19 '18 at 14:06
  • @azro, "24 fields" - it doesn't sound good either, huh? I am curious to know where these numbers came from :) – Andrew Tobilko Jul 19 '18 at 19:24
  • `Classes are so huge` if there is more than 10 attributes, your model is not well-designed so ... ;) – azro Jul 19 '18 at 19:29

3 Answers3

4

There are frameworks that do a mapping between objects of different classes. Chech out the comments.

If you don't want to use a third-party library, you could write an over-simplified version of what those frameworks offer.

For instance, if the names of the fields are identical, and the difference is only in types, we could write a method(A a, B b, Rules r) which would map a to b by the given rules1:

public static void copyFromAtoB(A a, B b, Map<String, Function<Object, Object>> rules) throws NoSuchFieldException, IllegalAccessException {
    for (Field f : B.class.getDeclaredFields()) {
        final String fName = f.getName();
        final Object aValue = A.class.getDeclaredField(f.getName()).get(a);

        f.set(b, rules.containsKey(fName) ? rules.get(fName).apply(aValue) : aValue);
    }
}

The rules2 tell what function we should apply to a field in order to set the value correctly.

If there is no rule for a field, we assume the types are compatible.

final A a = new A(5, 6);
final B b = new B();

final Map<String, Function<Object, Object>> rules = new HashMap<>();
rules.put("b", i -> Double.valueOf((int)i));  // int -> Double

copyFromAtoB(a, b, rules);

1 Yes, that's a reflection approach - it might be costly and over-engineered, but it seems pretty flexible.
2 Rules are not well-defined because we take an Object and return an Object (Function<Object, Object>).

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 1
    I suggest also to add calls to `setAccessible(true)` to both (read and write) the fields from within the loop, because it's possible that the `copyFromAtoB` method may be in a class that does not have direct access to the fields of class A and B (eg different package, or private fields etc). – sanastasiadis Jul 19 '18 at 16:29
2

If you try to use Apache BeanUtils, as suggested in one of the comments, you'll see that it has a copyProperties method, which can, to some degree, cast your types. For example, you can automatically get your double from an int, as it was in your example. However, if the properties really are not compatible, you'll get an exception and there seems to be no way to say you want to skip it. My approach is to extend the BeanUtilsBean class and add a method similar to copyProperties(), but with an extra argument: a list of excepted properties:

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

import org.apache.commons.beanutils.BeanUtilsBean;

public class MyBeanUtilsBean extends BeanUtilsBean {

    public void copyPropertiesExcept(Object dest, Object orig, String... exceptProperties)
            throws IllegalAccessException, InvocationTargetException {
        PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
        for (int i = 0; i < origDescriptors.length; i++) {
            String name = origDescriptors[i].getName();
            if ("class".equals(name)) {
                continue; // No point in trying to set an object's class
            }
            if (Arrays.asList(exceptProperties).contains(name)) {
                continue;
            }
            if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) {
                try {
                    Object value = getPropertyUtils().getSimpleProperty(orig, name);
                    copyProperty(dest, name, value);
                } catch (NoSuchMethodException e) {
                    // Should not happen
                }
            }
        }
    }
}

Then you can use that method to copy all properties except the different ones:

import java.lang.reflect.InvocationTargetException;

public class B {

    // ...

    public static B fromA(A objA) {
        B objB = new B();

        // Copy common properties
        try {
            new MyBeanUtilsBean().copyPropertiesExcept(objB, objA, "d");
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        // Explicitly copy specific properties
        objB.setD(new IntWrapper(objA.getD()));

        return objB;
    }
}

You can also try a complete working example.

Cos64
  • 1,617
  • 2
  • 19
  • 30
  • Good stuff here and very interesting approach. The way it works, please have in mind that it is necessary to have full set of setters and getters for the properties. – sanastasiadis Jul 19 '18 at 22:32
-1

I would create a constructor for class B that takes in the types you want and converts them.

class B {
   int a;
   Double b;

   public B(int a, int b) {
     this.a = a;
     this.b = new Double(b);
   }
}
Michael Shopsin
  • 2,055
  • 2
  • 24
  • 43
  • The point of the question here is not to copy the values one by one, but in a more dynamic way. – sanastasiadis Jul 19 '18 at 14:28
  • @sanastasiadis I would still go with explicit constructor method because it supports polymorphism and does not make assumptions about the structure of other types. Since B cannot extend A then B should not assume what the internal fields of A are. I've had a lot of bad experiences with reflection to get the internals of other classes and prefer to have objects use more explicit copying methods. – Michael Shopsin Jul 19 '18 at 14:29
  • 1
    According to the question: `the field names are exactly the same`. But anyway B does not have to assume anything. It can be a third party that assumes the internal structures of A and B are identical. – sanastasiadis Jul 19 '18 at 14:34