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.
6
  • There are lots of possible methods to archieve that. Commented Jul 19, 2018 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. Commented Jul 19, 2018 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 Commented Jul 19, 2018 at 14:02
  • There are libraries to do that. Check ALL the answers of: stackoverflow.com/questions/5937567/… Commented Jul 19, 2018 at 14:06
  • @azro, "24 fields" - it doesn't sound good either, huh? I am curious to know where these numbers came from :) Commented Jul 19, 2018 at 19:24

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

1 Comment

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

1 Comment

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

3 Comments

The point of the question here is not to copy the values one by one, but in a more dynamic way.
@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.
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.