Reputation: 837
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
Reputation: 81
you might want to use mapstruct to copy between java object. Great example here
Upvotes: 1
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