JavaFX Tips to Save Memory! Shadow Fields for Properties and Observables


A TreeTable control holding employees.

A TreeTable control holding employees.

   In the world of JavaFX the Properties API allows UI developers to bind values to UI controls. This capability is surprisingly easy, however when object models use properties too often an application can quickly run out of memory. I usually will write two separate objects such as a pojo class and a presentation model object. This technique is often used in Swing based applications. From a  JavaFX perspective, you could just create one object with properties to allow observers (listeners) to update values. This sounds fine right? Not exactly because the main issue is when all of the object’s (pojo) attributes (fields) are properties which also wrap actual values the programmer (user of the api) may not want to bind or use properties at all, but only want to access the actual values. So, what is a JavaFX developer to do?

I often go to the blog Pixel Perfect by Dirk Lemmermann who will often post very useful JavaFX tips. Recently, Dirk blogged about how to save memory using an interesting pattern called Shadow Fields.  To see his post please visit his blog entry JavaFX Tip 23: Save Memory! Shadow Fields for Properties.. Dirk’s JavaFX tip does help solve the problem mentioned above (lessening the heap), however I noticed boilerplate code that has to exist to provide (a smart determination) to the caller of whether the return is the actual object or the property wrapper object. For example, instead of returning an IntegerProperty object when calling get or set methods, the code will return an int or Integer value, thus saving memory. Furthermore, the code declares two variable to hold one of the two value types. For example:

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

I felt I could make things more concise and possibly saving more memory. And reducing the boilerplate code. I decided to create an interface with Java 8’s default methods that will handle managing actual values and properties. The user of the API will simply create a domain class that implements the following interface:

Interface PropertyAccessors

An interface providing accessor methods to provide smart determination of actual object value or the JavaFX property wrapper object. The user of the API must implement one method called getModelProperties() which returns a Map of property name (String) and value (Object). The value can be an actual object or a Property type object. The code below also will support Observable lists.

package com.jfxbe;

import javafx.beans.property.Property;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Provide default methods to support the similar
 * capability of the shadow fields pattern.
 * To save memory object values don't have to be
 * wrapped into a Property object when using getters
 * and setters, however when calling property type methods
 * values will be wrapped into a property object.
 *
 * Default methods for Observable lists are provided too.
 *
 * Created by cpdea on 4/3/16.
 */
public interface PropertyAccessors {

    Map<String, Object> getModelProperties();

    default <T> T getValue(String name, Object defaultVal) {
        Object p = getModelProperties().get(name);
        p = p==null ? defaultVal : p;
        return (T) ((p instanceof Property) ? ((Property) p).getValue(): p);
    }

    default void setValue(String name, Object value) {
        Object p = getModelProperties().get(name);
        if (p instanceof Property) {
            ((Property)p).setValue(value);
        } else {
            getModelProperties().put(name, value);
        }
    }

    default <T> T refProperty(String name, Class propClass, Class rawValType) {
        Object p = getModelProperties().get(name);
        Property prop = null;

        try {

            if (p == null) {
                Class[] constructorTypes =
                        new Class[]{Object.class, String.class};
                Constructor<Property> propConstr =
                        propClass.getDeclaredConstructor(constructorTypes);
                prop = propConstr.newInstance(this, name);
            } else if (rawValType.isInstance(p)) {
                Class[] constructorTypes = new Class[]{Object.class,
                        String.class, rawValType};
                Constructor<Property> propConstr =
                        propClass.getDeclaredConstructor(constructorTypes);
                prop = propConstr.newInstance(this, name, p);
            } else {
                prop = (Property) p;
            }
            getModelProperties().put(name, prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) prop;
    }

    default <T> List<T> getValues(String name, List<T> defaultValue) {
        Object p, o = getModelProperties().get(name);
        p = o;
        o = o==null ? defaultValue : o;
        if (!o.equals(p)) {
            getModelProperties().put(name, o);
        }
        return  (List<T>) o;
    }

    default <T> void setValues(String name, List<T> newList) {
        Object list = getModelProperties().get(name);
        if (list == null || !(list instanceof ObservableList)) {
            getModelProperties().put(name, newList);
        } else {
            // Should the list be totally replaced? below clears and adds all items
            ObservableList<T> observableList = (ObservableList<T>) list;
            observableList.clear();
            observableList.addAll(newList);
        }
    }

    default <T> ObservableList<T> refObservables(String name) {
        List list = (List) getModelProperties().get(name);

        if (list == null) {
            list = FXCollections.observableArrayList(getValues(name, new ArrayList<>()));
            getModelProperties().put(name, list);
        }

        if (! (list instanceof ObservableList)) {
            list = FXCollections.observableArrayList(list);
            getModelProperties().put(name, list);
        }

        return (ObservableList<T>) list;
    }
}

Employee Class

A class named Employee which implements the PropertyAccessor interface. Below you will notice the property name of each field is declared using a public static final String. For example, the employee’s name is:

public static final String NAME_PROPERTY = “name”;

For the accessor methods such as getters, setters and xyzProperty() you will notice the calls to the default methods in the PropertyAccessor interface.

package com.jfxbe;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A hybrid domain and model object using the shadow field pattern to save memory.
 * Created by cpdea
 */
public class Employee implements PropertyAccessors{

    /** This is a map to hold properties and observables */
    private Map<String, Object> modelProperties;

    public static final String NAME_PROPERTY = "name";
    public static final String POWERS_PROPERTY = "powers";
    public static final String SUPERVISOR_PROPERTY = "supervisor";
    public static final String MINIONS_PROPERTY = "minions";

    public Employee(String name, String powers) {
        setName(name);
        setPowers(powers);
    }

    @Override
    public Map<String, Object> getModelProperties() {
        if (modelProperties == null) {
            modelProperties = new HashMap<>();
        }
        return modelProperties;
    }

    public final String getName() {
        return getValue(NAME_PROPERTY, "");
    }
    public final void setName(String name) {
        setValue(NAME_PROPERTY, name);
    }
    public final StringProperty nameProperty() {
        return refProperty(NAME_PROPERTY, SimpleStringProperty.class, String.class);
    }

    public String getPowers() {
        return getValue(POWERS_PROPERTY, "");
    }

    public final StringProperty powersProperty() {
        return refProperty(POWERS_PROPERTY, StringProperty.class, String.class);
    }

    public final void setPowers(String powers) {
        setValue(POWERS_PROPERTY, powers);
    }

    public final Employee getSupervisor() {
        return getValue(SUPERVISOR_PROPERTY, null);
    }

    public final ObjectProperty<Employee> supervisorProperty() {
        return refProperty(SUPERVISOR_PROPERTY, ObjectProperty.class, Employee.class);
    }

    public final void setSupervisor(Employee supervisor) {
        setValue(SUPERVISOR_PROPERTY, supervisor);
    }

    public final List<Employee> getMinions() {
        return getValues(MINIONS_PROPERTY, new ArrayList<>());
    }

    public final ObservableList<Employee> minionsObservables() {
        return refObservables(MINIONS_PROPERTY);
    }

    public final void setMinions(List<Employee> minions) {
        setValues(MINIONS_PROPERTY, minions);
    }

}

Conclusion

So, there you have it, a solution to in an attempt to eliminate two variables and other boilerplate code. I haven’t actually tested the code with a large amount of data, so maybe in another post or some lucky reader will create a test comparing all three (object with all properties, Dirk and mine) implementations.

The possible downside to this approach is probably serializing the object when used with RMI servers. I’m sure there are other possible downsides, but for now most use cases this might be easier and cleaner code to deal with.

Please feel free to comment. Subscribe if you like this article. 🙂

References:

https://dlemmermann.wordpress.com
https://docs.oracle.com/javase/8/javafx/api/javafx/beans/property/Property.html
http://blog.netopyr.com/2011/05/13/javafx-properties/
https://projectlombok.org
http://www.jgoodies.com/freeware/libraries/binding

Advertisements

5 thoughts on “JavaFX Tips to Save Memory! Shadow Fields for Properties and Observables

  1. Dirk Lemmermann

    But now every single model object contains a hashmap …… isn’t that a lot of overhead memory-wise? Especially when you consider that applications often create thousands if not even millions of these objects? The goal of the shadow-field pattern is to save memory, I do not think this will happen here anymore.

  2. Dirk Lemmermann

    The “powers” and “supervisor” properties in the Employee class do not work because you are passing StringProperty.class instead of SimpleStringProperty.class.

  3. Pingback: Shadow Fields vs. Property Accessor Interface | Pixel Perfect

  4. Pingback: Shadow Fields vs. Property Accessor Interface Round 2 | CarlFX Blog

  5. Pingback: Shadow Fields vs. Property Accessors Interface Round 3 | CarlFX Blog

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s