Sunday, August 1, 2010

The Java needs language-level property support (and a reset)

I find myself increasingly bewildered by some of the design choices in the Java world. The core language's use of accessor methods is a notable example.

Not that properties are the only bewildering Java design choice.

Since the very early Java days, this has been discouraged:

public class Something { 
  public int prop;
}

public class SomethingUser {
  public void outputThing(Something thing) {
    System.out.println("Thing " + thing + " has value " + thing.prop);
  }
}

in favour of an accessor-driven approach:


public class Something { 
  private int prop;
  
  public int getProp() {
    return prop;
  }

  public void setProp(int prop) {
     this.prop = prop;
  }
}

public class SomethingUser {
  public void outputThing(Something thing) {
    System.out.println("Thing " + thing + " has value " + thing.getProp());
  }
}

... which is verbose and annoying. On the other hand there are good arguments for why you want accessors - essentially they're a large part of the difference between using "structs and procedures" and using objects and messaging (OOP). Both can be good models, but if you're going to try for an OOP approach you need ways to trigger side effects when values change, or to return generated values as the internal representation of an object changes, both of which require accessors.

One might wonder, though, if something this important should be something the language can do for you in simple cases, so you only have to hand-write the accessors that do non-default things. Like the following (wishful thinking) pseudo-Java:

public class Something { 
  public property int prop;
}

public class SomethingUser {
  public void outputThing(Something thing) {
    System.out.println("Thing " + thing + " has value " + thing.getProp());
  }
}

This wishful thinking becomes more significant when the need to introduce property change listeners is considered. Java has a system for listening for changes to properties of objects and triggering events when these properties change. It's entirely at the library level, though, with no language support. A property change enabled version of our first example might look like this:

public class Something { 
  private static PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
  private int prop;
  
  public int getProp() {
    return prop;
  }

  public void setProp(int prop) {
     changeSupport.firePropertyChange("prop", this.prop, this.prop = prop);
  }

  public void addPropertyChangeListener(PropertyChangeListener listener) {
    changeSupport.addPropertyChangeListener(listener);
  }

  public void addPropertyChangeListener(String propName, PropertyChangeListener listener) {
    changeSupport.addPropertyChangeListener(propName, listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    changeSupport.removePropertyChangeListener(listener);
  }

  public void removePropertyChangeListener(String propName, PropertyChangeListener listener) {
    changeSupport.removePropertyChangeListener(propName, listener);
  }
}

public class SomethingUser {
  public SomethingUser(Something thing) {
    thing.addPropertyChangeListener("prop", new PropertyChangeListener() {
      public propertyChange(PropertyChangeEvent evt) {
        outputThing((Thing)evt.getSource());
      }
    });
  }

  public void outputThing(Something thing) {
    System.out.println("Thing " + thing + " has value " + thing.getProp());
  }
}

Getting verbose, isn't it? Given how important the property change system is in Java, one might wonder why there's no language integration. Imagine a "bean" modifier that inserted property change support (we'd do this with a mixin class, but Java doesn't have multiple inheritance or mixins, so we need language support) and enabled property change notification on declared properties:

public bean class Something { 
  public property int prop;
}

public class SomethingUser {
  public SomethingUser(Something thing) {
    thing.addPropertyChangeListener("prop", new PropertyChangeListener() {
      public propertyChange(PropertyChangeEvent evt) {
        outputThing((Thing)evt.getSource());
      }
    });
  }

  public void outputThing(Something thing) {
    System.out.println("Thing " + thing + " has value " + thing.getProp());
  }
}

The property client side is still gruesome, but writing those property-change enabled beans is suddenly much, much less painful. You can remove the "property" modifier and implement the accessors yourself if you need to do anything interesting, but the 99% of boilerplate crap you have to write usually just goes away.

Rather better thought out proposals have been made by many others, such as this one, though like most of them the linked proposal fails to consider bean binding and change notification.

Unfortunately, rather than solving real-world pain points for Java developers, the Java language appears to be stuck in arguments over whether closures should be supported, endless nattering about reified vs erasing generics, etc. These are all useful, but there are so many pain points in the library that could be dealt with by a few language extensions.

Even a decent, standard, core way of doing mixins would help a lot. As things stand you have numerous aspect-oriented programming frameworks that half-solve the problem, but also tend to conflict with each other, add complexity, require annotation processors or other code generation, and generally be a pain to use.

You know Java is in a bad way when you start to miss coding C++.

(Yes, I know I can do this with Groovy. Sort-of. Of course, Groovy doesn't play well with some other Java features like anonymous inner classes, requires a whole new set of tools and tool support enhancements, its own dev env and compiler, etc. Why not fix the core language?)

1 comment:

  1. Agreed! I would be happy if Project Lombok gets integrated into Java 9 or 10...

    :-/

    The weird thing is that C++ doesn't have this feature either. I think the problem is that so many Java frameworks force you to write those damn getters and setters.

    ReplyDelete