How to quickly populate a Java object with data from another, unrelated object?
How can I go about populating a simple Java object with data from any other arbitrary object?
For example, I can end up with a Document
object whose children nodes I can iterate through and I want to set same-named properties in another object with the node's values.
I work primarily in dynamic languages and I think I am hung up on how this would work in perl or javascript and can't get my head out of the dynamic gutter long enough to see this clearly.
I know I could do something like (pseudo code)
while (key = nextKey) {
if (key.name == "fooBar") {
object.setFooBar(key.value);
} else if (key.name == "bazQux") {
object.setBazQux(key.value);
}
...etc...
}
But that just doesn't feel good, and feels awful when the number of properties or complexity increases.
In a dynamic language, i would do something like:
while (key = nextKey) {
object.setField(key.name, key.value);
// or even
object.[key.name] = key.value;
}
where setField
could be a dispatch table with code references. I know I don't have the luxury of every object being a hash by default, but I'm looking for general advice. How would you do it?
A switch/case would be a little better, but开发者_高级运维 java complains about it not enjoying Strings in such statements. Would enums be a solution? </blind stabbing for answers>
I've looked into using reflection in order to implement some sort of automatic dispatch table, but that strikes me as being looked down upon and that there has to be a better way.
Thanks for any insight.
This topic is discussed before: any tool for java object to object mapping? Especially the post of Pascal Thivent contains a lot of API's. Surely there must be one in between which suits your needs.
You could also decide to homegrow one based on the Reflection API or maybe the JavaBeans API (with under each the PropertyEditor
). But I think this is going to take more time than you would expect to get it all robust.
Another vote for reflection. What you're trying to do here is automatically bind untyped String data to typed Java variables. That's simply going to require reflection.
The reason reflection is "looked down upon" is because generally speaking, blindly binding untyped String data to typed Java variables is "looked down upon". In many cases it's better to (for example) have your object explicitly pull out only the data it needs. For example:
this.fooBar = doc.get("fooBar");
this.bazQux = doc.get("bazQux");
// ... etc.
You might say, "But now if I add a sysBiz
entry to my document, I have to remember to add an extra line to my constructor. In a dynamic language it would Just Work[tm]!" That's true, but in Java you have to add your sysBiz
member variable, your getters and setters, and all the other logic around sysBiz
anyway, so an extra line in your constructor or deserialization section isn't all that much of a nuisance. Plus, it makes explicit which parts of the document you care about, so if somebody adds a zigBlog
entry to the document, it won't cause problems with your automatic binding (e.g. something that looked like object.setField(key, val)
) throwing a runtime error about no zigBlog
field existing.
All that said, there are times when automatically binding untyped String data to typed Java variables is the right way to go, and reflection is the right tool to use in that scenario. Make sure that automatic binding really is the best choice for the data structures you've got, but once you're sure, there's no need to hesitate to use reflection just because it's "looked down upon" or is a slightly more advanced section of the language. That's exactly what it's there for.
The Java way would be something like:
public interface Trasnformer<F, T>
{
T transformFrom(F original);
}
then have classes like:
public class IntegerStringTransofromer<Integer, String>
{
public String transformFrom(Integer original)
{
// code
}
}
I am sure you can come up with something more like what you are really trying to do, but it isn't really a natural way of doing it in Java. Generally when you switch to different styles of languages you need to switch the way you approach problems. I am sure a perl program written as a Java program would be a strange thing... just as a Java program written as a perl one would be.
Sigh... despite m,y better judgment... here is a solution that does what you ask (well not exactly but it is a start)... all I ask is that you do not use it!
class Copier
{
public static void copyFromTo(final Object source,
final Object dest)
{
final Class sourceClass;
final Class destClass;
final Field[] sourceFields;
sourceClass = source.getClass();
destClass = dest.getClass();
sourceFields = sourceClass.getDeclaredFields();
for(final Field field : sourceFields)
{
copyField(field, source, dest, destClass);
}
}
private static void copyField(final Field field,
final Object source,
final Object dest,
final Class destClass)
{
final String fieldName;
final String methodName;
fieldName = field.getName();
methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
try
{
final Method method;
method = destClass.getMethod(methodName, field.getType());
field.setAccessible(true);
method.invoke(dest, field.get(source));
}
catch(final NoSuchMethodException ex)
{
// ignore it
ex.printStackTrace();
}
catch(final IllegalAccessException ex)
{
// ignore it
ex.printStackTrace();
}
catch(final IllegalArgumentException ex)
{
// ignore it
ex.printStackTrace();
}
catch(final InvocationTargetException ex)
{
// ignore it
ex.printStackTrace();
}
}
}
and here is how to use it (again, please do not).
public class Main
{
public static void main(String[] args)
{
final B b;
b = new B();
Copier.copyFromTo(new A(), b);
System.out.println("b.a = " + b.getA());
System.out.println("b.b = " + b.getB());
System.out.println("b.c = " + b.getC());
System.out.println("b.d = " + b.getD());
System.out.println("b.e = " + b.getE());
}
}
class A
{
private String a = "This";
private String b = "Is";
private String c = "A";
private String d = "Bad";
private String e = "Idea";
}
class B
{
private String a;
private String b;
private String c;
private String d;
private String e;
public void setA(final String val)
{
a = val;
}
public void setB(final String val)
{
b = val;
}
public void setC(final String val)
{
c = val;
}
public void setD(final String val)
{
d = val;
}
public void setE(final String val)
{
e = val;
}
public String getA()
{
return (a);
}
public String getB()
{
return (b);
}
public String getC()
{
return (c);
}
public String getD()
{
return (d);
}
public String getE()
{
return (e);
}
}
Will serializing the objectA to XML and deserialzie XML to objectB solve your problem?
If so, take a look at Simple framework. Will make your XML serialization a piece of cake. If not, then nevermind, skip my answer :)
I think the MethodUtils library from apache is exactly what you're looking for
Look at the classes in java.lang.reflect. The reflection tools let you build the names of (classes, ) methods and fields, and then make the needed calls. Make sure that if you call "setFoo(...)", you have the right data type, though. Sorry. Welcome to Java :-)
Apache Commons BeanUtils has something similar for moving data between beans (mind you, reflection-based!)
You could easily do something fitting your problem with reflection, unit test the hell out of it and then -- if the performance is satisfactory, leave it be. If not, you can potentially think about using code generation based on XML schemas, or use something like JAXB.
There's really nothing wrong with using reflection for things like these if you isolate the magic appropriately, make sure you handle edge cases well, and are happy with performance for your application.
So... reflection is not as evil as it's portrayed most of the time, provided you use your brain, as usual.
Let me explain that the only way to do something similar to this in the Java runtime is to use reflection. That is not to say that you must use the Java language to achieve this though! I believe Groovy allows you write code very similar to the second, and have it compile into bytecode using reflection instead.
EDIT: That is to say, to do it generically. One could come up with a few patterns for transforming one class to another. But to generically map fields in one object to fields with matching names in another, that is a different matter all together. It can be done in Java. I have done it before in Java. It is very painful. Let something else do most of that heavy lifting for you.
It's been a while, but here's an attempt in Groovy:
// Assuming document implements Map.
document.each {
// At this point, 'it' is a Map.Entry.
// Make sure the property exists on the target object.
if (object.properties.keySet().contains(it.key)) {
// Set the property to the value from the map entry.
object."${it.key}" = it.value
}
}
Spring BeanUtils also can be used for copying data from one objet to others.
I would like at the apache beans library (http://commons.apache.org/beanutils/), it is probably your best bet. The [copyProperties][1] method is probably what you are looking for.
Based on my experience, these are the problems you may experience:
- Objects that are not compatible between themselves. The method will try to convert native types such as int to String and so on, but it won't convert complex types. The class BeanUtilsBean is the way for you to provide custom converters;
- I don't think you can control what can be copied or not (the problem with the reflection approach is that it happens under the hood - you may add a parameter A to a class and the same parameter A to another class, they are not compatible, and all of a sudden your code won't work anymore).
Another option, still using reflection, is to use annotations in your source code to control what can be copied or not. You could add an annotation to your getter method to indicate that it can be copied, and you can provide a class to the annotation that will perform the type conversion if needed. The same applies to your setter method, you could allow it only to be set if the annotation is in there.
If you really want to go far and want to initialize your copied class in its constructors, you could use parameter annotations in the constructors parameters to do something like this:
public class YourClass {
public YourClass(
@DynamicParameter(name = "id") String id,
@DynamicParameter(name = "name") String name
)
}
...and then read the constructor from the class using reflection, and reading the annotations using the method getParameterAnnotations. Still, the commons beans library would provide you methods to convert from native types to native types.
It all depends on how far you want to go.
[1]: http://commons.apache.org/beanutils/v1.8.2/apidocs/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object, java.lang.Object)
精彩评论