JavaFX Tips to Save Memory! Shadow Fields for Properties and Observables
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!
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
Reference: | JavaFX Tips to Save Memory! Shadow Fields for Properties and Observables from our JCG partner Carl Dea at the Carl’s FX Blog blog. |