JavaFX Tip 23: Save Memory! Shadow Fields for Properties.

Properties and property bindings introduced in Java 8 are extremely useful programming concepts. They are especially useful when you are developing user interfaces. In fact they are so useful that developers have fallen victim to the idea that everything should be a property instead of a primitive. Unfortunately they easily forget that properties such as SimpleLongProperty are much bigger objects than standard types such as Long. And of course they are much bigger than primitive data types such as long.

In one of my current projects pretty much every model object used by the client is composed of properties. For many of these model objects it is the right approach because they will be edited / modified via JavaFX controls. But there are also many model objects that are not edited. They exist to support the rendering of schedules in the FlexGanttFX control. These objects do not need to be observed, hence they do not need to provide properties … but they do and they waste a lot of memory because they do.

One way to fix this would be to refactor the model classes and to get rid of all properties, but then again we might want to use these objects in a future release in a different context and then we might need properties because we want to edit them directly. What to do?

Shadow Fields

The solution to this problem is something I recently saw Gerrit Grunwald do in the code of his Medusa project and a pattern that was described by Mr. Properties himself Michael Heinrichs.  The pattern makes use of a “shadow field” that is of the same type as the wrapped object inside the property. When using this pattern a property will only be created when it is really needed (“when somebody asks for it”).

Example

In this example we want to manage an attribute called “title”. We need a setter, a getter, and the property accessor.

private String _title = "Untitled"; // shadow field

private StringProperty title;

public final String getTitle() {
    return title == null ? _title : title.get();
}

public final void setTitle(String newTitle) {
    if (title == null) {
        _title = newTitle;
    } else {
        title.set(newTitle);
    }
}

public final StringProperty titleProperty() {
    if (title == null) {
        /// !!!! pass shadow field to constructor
        title = new SimpleStringProperty(this, "title", _title);  
    }

    return title;
}

By using this pattern I was able to bring down the memory footprint from 310 MB to 250 MB for a specific use case in my project. The saved memory is ten times the total memory my computer had when I was a student. Just think about that!

22 Comments

  1. Obviously this means even more boilerplate code for properties. It would be great if the IDE plugins would support the shadow field strategy, too. For Eclipse I have already submitted an enhancement ticket, so I hope Tom Schindl will look at it 🙂

  2. Yes. That was my first reservation; loads of boilerplate code. And even if the EDI will generate this, it will still greatly impact the source code. We (Java) really need to find a better way.

  3. Direct language support would be great. But then I would love to be able to access a property path instead of just the property of a given instance.

    In my application I generate static innerclasses and some bytecode-magic, allowing me to access properties like this:

    property = PickupRequestController.p.pickupRequest.pickupAddress.address1
    String address1 = property.get(controllerInstance)

  4. I use a little bit another way, introduce only one simple Property, all others come as Bindings by request, for example:

    private String title;

    private final BooleanProperty isChanged = new SimpleBooleanProperty();

    public String getTitle() {
    return title;
    }

    public void setTitle(String title) {
    this.title = Objects.requireNotNull(title);

    fireChanged();
    }

    public StringProperty createTitleBinding() {
    return Bindings.createStringBinding(this::getTitle, isChanged);
    }

    private void fireChanged() {
    isChanged.set(!isChanged.get());
    }

  5. @Dmitry This looks like it does not save a whole lot of memory (which is the idea behind the shadow fields). You still have one full property for each attribute. And just because it is a BoolenProperty does not mean it is tiny.

  6. I haven’t looked at Lombok in awhile, but it wonder if an annotation could be created to apply the boilerplate code.

    -Carl

  7. Dirk,
    Should the setTitle() method’s else statement call titleProperty().set(title) instead of title.set(title)
    ?

    Npe would occur I believe.
    Thanks for the tip!
    Carl

  8. Lombok (or at least a custom annotation) will work. I have not used lombok myself, somehow I keep steering away from it, but people who did felt it to be more of a kludge compared to used an extended language like Groovy or Xtend (especially in IDE support).

  9. Dirk,
    My mistake!
    Whoops it was late night for me. I see title is checked first. I thought title was _title. I’m used to properties having a suffix xyzProperties.
    Very cool trick.
    I see now the bean can be use without properties ever created. But as soon as u use titleProperty() one gets created.
    Thanks for the tip!
    Carl

  10. Dirk,
    Not to be nit-picky, but I think some lines didn’t compile.

    public final String getTitle() {
    title == null ? return _title : title.get();
    }
    should be:
    public final String getTitle() {
    return title == null ? _title : title.get(); // <— change
    }

    Also,
    public final StringProperty titleProperty() {
    if (title == null) {
    /// !!!! pass shadow field to constructor
    title = new StringProperty(this, "title", _title);
    }

    return title;
    }

    Should be:
    public final StringProperty titleProperty() {
    if (title == null) {
    /// !!!! pass shadow field to constructor
    title = new SimpleStringProperty(this, "title", _title); //<– change
    }

    return title;
    }

    It's 11:16pm If i'm incorrect I'm probably coding in another language 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s