schrobe
schrobe

Reputation: 837

How to update all field of one java object with the values from another one?

Assume the following class:

public class TestClass {
    String attr1;
    String attr2;
    String attr3;
}

and a client code like:

final TestClass testClassA = new TestClass();
testClassA.attr1 = "1";
testClassA.attr1 = "2";
testClassA.attr1 = "3";

final TestClass testClassB = new TestClass();

I would like to find way/method that updates testClassB with all the values of testClassA.

testClassB.updateAll(testClassA)

One such solution would be:

public void updateAll(TestClass testClass) {
    this.attr1 = testClass.attr1;
    this.attr2 = testClass.attr2;
    this.attr3 = testClass.attr3;
}

Now, here comes the thing: I would like to not have to write this method manually for it to be less resilient when e.g. a new attribute is added. In this case I might forget to add it to the update-method.

The solution does not need to assign the values directly, in fact I'd prefer it to call setter methods.

It is also possible for me to use any 3rd party frameworks out there like Lombok. I am looking for something like the @RequiredArgsConstructor, however I need the new object to be updated and not created.

So something like a @RequiredArgsSetter or a Object.updateInto(Object1 o, Object2 o) method, but again, it should not create a new object but simply update all the fields of an existing object.

Bonus points, if it is somehow possible to annotate the fields which should be included or excluded from being set.

Upvotes: 3

Views: 2667

Answers (2)

sone
sone

Reputation: 81

you might want to use mapstruct to copy between java object. Great example here

Upvotes: 1

DarkMatter
DarkMatter

Reputation: 1017

I found your question interesting and decided to try it out. Here is a solution using reflection. It looks for fields that match by name and type and which are not excluded by annotation, then sets the values of any matching fields.

Disclaimer: I haven't thoroughly tested this, only lightly. It may need some work. It also doesn't use setter methods but instead just sets the field value.

Attribute copying method:

public class AttrCopy {

    public void copyAttributes(Object from, Object to) throws IllegalAccessException {
        Map<String, Field> toFieldNameMap = new HashMap<>();
        for(Field f : to.getClass().getDeclaredFields()) {
            toFieldNameMap.put(f.getName(), f);
        }
        for(Field f : from.getClass().getDeclaredFields()) {
            Field ff = toFieldNameMap.get(f.getName());
            f.setAccessible(true);
            boolean include = f.getDeclaredAnnotation(AttrCopyExclude.class) == null;
            if(include && ff != null && ff.getType().equals(f.getType())) {
                ff.setAccessible(true);
                ff.set(to, f.get(from));
            }
        }
    }
}

Annotation to exclude fields:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AttrCopyExclude {
}

Test classes:

public class ClassA {
    private String attribute1;
    private int attribute2;
    private int attribute3;
    private String attribute4;
    private String attribute5;

    // toString()
}

public class ClassB {
    private String attribute1;
    private int attribute2;
    private String attribute3;
    @AttrCopyExclude
    private String attribute4;
    private String attribute6;

    // toString()
}

Test code:

public class Tester {
    public static void main(String[] args) throws IllegalAccessException {
        ClassA classA = new ClassA("aaa", 123, 456, "ddd", "eee");
        ClassB classB = new ClassB("111", 789, "333", "444", "555");

        System.out.println("Before");
        System.out.println(classA);
        System.out.println(classB);

        new AttrCopy().copyAttributes(classB, classA);

        System.out.println("After copy A -> B");
        System.out.println(classA);
        System.out.println(classB);
    }
}

Test output:

Before
ClassA{attribute1='aaa', attribute2=123, attribute3=456, attribute4='ddd', attribute5='eee'}
ClassB{attribute1='111', attribute2=789, attribute3='333', attribute4='444', attribute6='555'}
After copy B -> A
ClassA{attribute1='111', attribute2=789, attribute3=456, attribute4='ddd', attribute5='eee'}
ClassB{attribute1='111', attribute2=789, attribute3='333', attribute4='444', attribute6='555'}

Attributes 1 and 2 are copied. 3 is excluded as type does not match. 4 is excluded by annotation. The last is excluded as the name doesn't match.

Upvotes: 2

Related Questions