the blog for developers

Beautiful Java: Reflection and the BeanCopier

When reading about reflection on the beautiful code website I thought about solving some problems with reflection and finding new solutions to old problems. One problem is boring code when writing a copy constructor. A copy constructor is a constructor which takes another object and copies it’s attributes. This is often useful when copying objects or moving objects to different system layers. One example would look like this:

public class Employee {
  public Employee(Employee other) {
      this.name = other.name;
  }
}

There are some pitfalls, especially that you only create shallow copies. And there is always clone() and Serializable, which can also be used to create deep copies without such pitfalls. JBoss provides a fast implementation of this technique. But often copy constructors are quite useful if you’re careful and know what you do. I recently had a discussion on how to support domain objects in different parts of a system. One part might call an entity Employee, another part might call it Person. To make them talk together you can either create a composite, an adapter, a wrapper or use a copy constructor (or find a different solution). In such a case a shallow copy is all you want, because the new object should have the same “identity” as the first and not be a copy.

public class Employee {
  public Employee(Person person) {
      this.lastName = person.getLastName();
      this.name= person.getFirstName();
  }
}

This should be easier to implement. Copying bean attributes for large objects isn’t much fun, leads to long constructors and unmaintanable code. What about changes in Person? New attributes? So I tinkered with Java reflection and ended with this:

 public Employee(Person person) {
    BeanCopier copier = new BeanCopier();
    copier.copy(person, this, "name:firstName", "age:");
 }

I’ve written a BeanCopier which can – well you guessed it – copy beans. The syntax is easy to learn. A BeanCopier has a copy method which takes a source, a copy and the attributes which should be specially handled. By default all attributes which have the same name and type in source and copy are copied. But if they have different names, like name and firstName, you need to specify “name:firstName”. If a property shouldn’t be copied, you can specify “property:” without a target attribute.

The copy method looks like this (the generics are not needed but I was also tinkering with caching where it was quite handy):

public <T, U>void copy(U source, T destination, String... attributes) {
  if (destination.getClass().getName().equals(source.getClass().getName())) {
    // copy all fields
  } else {
    // copy all bean properties
    try {
      BeanInfo sourceInfo = Introspector.getBeanInfo(source.getClass());
      PropertyDescriptor[] sourceDescriptors = sourceInfo.getPropertyDescriptors();
      for (PropertyDescriptor descriptor : sourceDescriptors) {
        String name = descriptor.getName();
        if (!"class".equals(name)) {
          String attribute = this.find(attributes, name);
          if (attribute == null) {
            copyAttribute(source, destination, name);
          } else if ((attribute.indexOf(':') >= 0) && !attribute.endsWith(":")) {
            int colonIndex = attribute.indexOf(':');
            String sourceName = attribute.substring(0, colonIndex);
            String copyName = attribute.substring(colonIndex + 1);
            copyAttribute(source, destination, sourceName, copyName);
          }
        }
      }
    } catch (IntrospectionException e) {
      e.printStackTrace();
    }
  }
}

and the copyAttribute method:

private void copyAttribute(Object source,
      Object copy,
      String sourceName,
      String copyName) {

  PropertyDescriptor writer;
  PropertyDescriptor reader;
  try {
    writer = new PropertyDescriptor(copyName, copy.getClass());
    reader = new PropertyDescriptor(sourceName, source.getClass());

    Object value = reader.getReadMethod().invoke(source);
    Class sourceType = reader.getPropertyType();
    Class destionationType = writer.getPropertyType();
    if (sourceType.getName().equals(destionationType.getName())) {
      writer.getWriteMethod().invoke(copy, value);
    }
}

The code is rough but works. The type test can be improved to work with super types.BeanCopier can be used outside of constructors, often a wise choice. And be careful with “copying” other objects, you might only create a shallow copy.

Of course, I could have taken a look at commons BeanUtils, but isn’t it much more fun to tinker and find a nice solution yourself sometimes?

Thanks for listening.

You can leave a Reply here. Of course, you should follow me on twitter here.

You can share this post!
Do you want to tell others about this article? Use the social bookmark icons to submit this artice to the service of your choice. Thanks.

About the author: Stephan Schmidt has more than 15 years of internet technology experience and 10 years experience in agile. He was head of development, consultant and CTO and is a speaker, author and blog writer. He specializes in organizing and optimizing software development helping companies by increasing productivity with lean software development and agile methodologies. Want to know more? All views are only his own.
Leave a reply.

Comments

ben

copy = xstream.fromXML(xstream.toXml(object));

Easier, faster, more robust.

stephan

Nice solution. But I guess it’s slower and more diffcult to understand for developers who don’t know xstream. It’s also more complex to configure. XStream is more robust though, you’re right. And of course your solution doesn’t copy properties but creates a new copy of an object. At least I would write a small wrapper with a copy copy method to change the semantics.

ben

XStream should be used by every java developer imho. It vastly simplifies copying of objects, serialisation, and is FASTER than normal serialisation without the drawbacks.

It could also be trivially enhanced to pass an object into it:

templateObject = new …
xstream.fromXml(templateObject, xml);

xstream.codehaus.org

There are many, many problems with doing your own approach – god knows I have tried. Its a large can of worms, as I am sure the xstream guys know.

There is another solution though – I think jboss have a similar object serialisation technology that would work similarly.

Lastly, if you want to restrict to beans, you can use the internal java classes xmlencoder,xmldecoder, but those are horrid imho.

stephan

I still don’t think xstream is faster than copying properties, I can’t see why it should.

And as I said, copying attributes to an object is something different than creating a copy of an object.

“There is another solution though – I think jboss have a similar object serialisation technology that would work similarly.”

If you’ve read the post, I wrote “JBoss provides a fast implementation of this technique.”

And yes, I have been using XStream in several projects.

And yes, I’ve said it is perhaps best to use Commons BeanUtils to copy properties.

:-)

[...] How-to: copying an object with several ways Published January 12, 2008 Java You may have this situation, you need to copy an object for some purpose. So you know the copy constructor but do you know how may ways to implement it? After I read Beautiful Java: Reflection and the BeanCopier, I complied those methods. Also you need to think about Law of Distributed Object Design: Don’t distribute your objects! and don’t distribute it!! [...]

null
stephan

Nice.

Leave a Reply

What people wrote somewhere else:

Additional comments powered by BackType

Guide to CodeMonkeyism

Over the last 4 years I wrote many articles on this blog. To make it easier for you to find the relevant ones, I've organized them into topics.

Top 10

6 reasons why my VC funded startup did fail

Go Ahead: Next Generation Java Programming Style

Java Interview questions: Write a String Reverser

The dark side of NoSQL

7 Bad Signs not to Work for a Software Company or Startup

Is Java dead?

Scala vs. Clojure

Never, never, never use String in Java

No future for functional programming in 2008 – Scala, F# and Nu

Clojure vs Scala, Part 2

Java Developer

Is Java Dead?

Go Ahead: Next Generation Java Programming Style

Be careful with magical code

All variables in Java must be final

Never, never, never use String in Java

Bending Java: More readable code with methods that do nothing?

NoSQL Guy

NoSQL: The Dawn of Polyglot Persistence

The dark side of NoSQL

Essential storage tradeoff: Simple Reads vs. Simple Writes

Sharding destroys the goals of your relational database

The unholy legacy of databases

Startup/CTO

Development Dream Teams

6 reasons why my VC funded startup did fail

American vs. European style of Software Development

12 Things to Reduce Your Lead Time and Time to Market

The high cost of overhead when working in parallel

Essential storage tradeoff: Simple Reads vs. Simple Writes

Job Seeker

Another Good (Java) Interview Question

7 Bad Signs not to Work for a Software Company or Startup

Java Interview questions: Write a String Reverser (and use Recursion!)

Java Interview questions: Multiple Inheritance

As a Manager: What I value in developers

Top 10 Tips (+1) to Get a Pay Raise

Agilist

What Developers Need to Know About Agile

5 Practices Better to Change in Your Scrum Implementation

Scrum is not about engineering practices

ScrumMaster and ZenMaster: The joke of certification

What is Trans-Scrum?