Gotcha! Watch out when overriding bean property accessors...

Whilst working on a bean deep cloning utility recently (more about that some other time) I got stuck for a while on some unexpected behaviour exhibited by java.beans.Introspector. I don't know many people who use this class directly, but it's heavily used by, amongst others, the Apache BeanUtils, so if you're using those you might still come across this issue.

The problem arises if you happen to have overridden bean property accessors in your classes.
For instance, assume you have a base class NumberHolder:

public class NumberHolder {
  private Number number;
  public Number getNumber() { return number; }
  public void setNumber(Number number) { this.number = number; }
}

Say that, within a certain context in your application, you know that the NumberHolder will always contain a Long. Then you could keep doing

Long long = (Long) myHolder.getNumber();

but that's rather ugly. So you might create a LongHolder:

public class LongHolder extends NumberHolder {
  @Override
  public Long getNumber() { return (Long) super.getNumber(); }
}

Now the million-dollar question is: Is LongHolder's number property writable or not? It certainly has a setter that accepts a Number and a getter that returns a Number (subclass), but on the other hand it doesn't have a setter that accepts a Long, and the property itself isn't a Long, either.
[Aside: I'm no longer sufficiently familiar with the intricacies of the Beans spec to be sure if the overriding getNumber() method isn't a violation. Any comments on that subject would be interesting.]

Whichever way, one would expect that the answer is either "yes" or "no". Unfortunately, though, the current reality appears to be: "Well, it depends."

Depends, in fact, on what kind of mood Class.getDeclaredMethods is in; to wit: in which order it returns the two getNumber() methods of LongHolder. Introspector retrieves these methods in order to be able to pair up possible getters and setters and thus determine the read-/writability of the bean properties.

If LongHolder.getNumber comes first, it is "overwritten" in the Introspector's data structure by NumberHolder.getNumber, for which a matching setter is found (correct name, same type), so the property is writable.
If, however, NumberHolder.getNumber comes first...well, you can, no doubt, guess what happens: no matching setter is found (there is no setNumber(Long) method in either of the classes) and so the property is not writable.

...and since the the order of the methods returned by Class.getDeclaredMethods is documented as being undetermined, this is a bug (6807471, in fact).

So if you're overriding property accessors in your code anywhere, you might want to bear this in mind.

Incidentally, it looks like there have been plenty of issues related to the indeterminate order of Class.getDeclaredMethods already, the bug reports for some of which contain very detailed discussions about the difficulty of mandating an ordering. Still, I'm surprised this hasn't been addressed yet.

Comments (0)

    Add a Comment