Copy fields between similar classes in java
I have pairs of classes where the fields of one is a subset of the fields of another and the getters of the superset classes are all predictably named (getFoo()
). Is there some way to efficiently copy all the common fields over from the superset class to the subset class, or at least auto-generate the code to do so.
I should note that:
- For various reasons, I can't edit the superset classes, nor can I just use them throughout to avoid having to do the data copy.
- I can potentially create new methods in the subset classes, but I can't change their fields.
- We have dozens of these pairs, and some of the classes have many many fields so doing this by hand is unwieldy to say the least.
- A colleague has come up with a method to create a generic copy method that uses java reflection to take any two classes, iterate through the fields as Strings, do string manipulation to determine the getter name, and then execute it to automatically set the field in the subset class. It's awful, but it appears to work. I'm really hoping there's a better way.
Edit: some simple code as requested
public class SuperClass {
private int foo;
private int bar;
private float bat;
public int getFoo() { return foo; }
public int getBar() { return bar; }
public float getBat() { return bat; }
}
public class SubClass {
private int foo;
private float bat;
}
//wanted
public static copySuperFieldsToSubMethod(Object super, Object sub) { ??? }
// also acceptable would be some way to autogenerate all the assignment
// functions needed
You could use the BeanUtils
class in the Spring Framework to do this. It may not necessarily be any more efficient than your reflection-based technique, but it's certainly simple to code. I expect that all you would need to do is:
BeanUtils.copyProperties(source, target);
Javadoc for this method is available at http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/BeanUtils.html#copyProperties(java.lang.Object,%20java.lang.Object)
If that doesn't suit, you could also consider using BeanWrapper
/ BeanWrapperImpl
in the Spring Framework to iterate through the properties of your classes. That would be simpler than using low-level reflection APIs.
Similar to the first answer, but to clarify - spring is not needed. Commons BeanUtils.copy properties(Object dest, Object orig)
http://commons.apache.org/beanutils/api/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object,%20java.lang.Object)
If you want to the task efficiently (in terms of runtime performance), then hand coding the copy using getters and setters is the way to go. Unless there is something funky about the getter or setter methods, their bodies will be inlined so that they are as fast as doing field assignments.
The reflective approach (e.g. using an existing class like BeanUtils
) is less coding, but probably an order of magnitude slower than calling getters and setters in a simple way. If you try to implement this yourself, you may find yourself with more work than you bargained for, especially if your reflective copy class / method has to cope with overloaded methods, inheritance, value conversion, boxing/unboxing and so on.
With the code generation approach, you need to balance the effort and complexity of implementing the code generation (using whatever technology you choose) versus the effort of writing the copy methods by hand. You probably probably won't break even with the code generation approach before 20 classes ... and many more if you are not familiar with the technology.
To copy based on fields rather than getters and setters, you can use Spring's ReflectionUtils.shallowCopyFieldState().
I'd write a simple java tool to autogenerate the source code for classes that can populate the the subsets fields with the common fields from superset. This tool will use reflection to get the names of the getter and setter methods. The rest are (trivial) String operations to "write" a source file in memory and store it to a *.java
file. Compile all this autogenerated files and add the class files to the classpath.
The class could look like this:
class AClassToBClassPopulator implements Populator {
@Overwrite
public void populate(Object superSet, Object subSet) {
subSet.setFieldA(superSet.getFieldA());
subSet.setFieldB(superSet.getFieldB());
// .. and so on. The method body is created through reflection
}
}
Can you provide some sample code from your app that depicts the scenario you have mentioned in your post? Right now reflection seems like the best way , since it lets you inspect the class members at runtime.
This is obviously a task for Java's reflection and while others have already suggested valid although maybe a bit heavyweight solutions, here's one more:
About a year ago I wrote a smallish JavaBean property modifier library called BeanPropertyController. While I don't specifically recommend it to anyone, I do think that the namesake class of the library (see source) can be used as a reference to adopt similar functionality to your needs. As a quick example, here's how I used BPC to do (almost!) what you're asking:
// somewhere in code...
SuperClass a = new SuperClass();
a.foo = 101;
a.bar = 102;
a.bat = 103f;
SubClass b = new SubClass();
b.foo = 201;
b.bat = 202f;
BeanPropertyController fromB = BeanPropertyController.of(b, ExtractionDepth.QUESTIMATE);
BeanPropertyController toA = BeanPropertyController.of(a, ExtractionDepth.QUESTIMATE);
// This is where the magic happens:
for (String propertyName : fromB.getPropertyNames()) {
toA.mutate(propertyName, fromB.access(propertyName));
}
a = (SuperClass) toA.getObject();
b = (SubClass) fromB.getObject();
System.out.println("SuperClass' foo="+a.foo+" bar="+a.bar+" bat="+a.bat);
System.out.println("SubClass' foo="+b.foo+" bat="+b.bat);
This prints out
SuperClass' foo=201 bar=102 bat=202.0
SubClass' foo=201 bat=202.0
So, what I suggest is that you go to the URL I linked and adapt this piece of code to your needs. I'm quite sure you don't need the various instantiation methods, default value providers etc. which I've included. And yes, BPC can be considered to be deprecated.
精彩评论