DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Java
  4. JavaFX Tips to Save Memory: Shadow Fields for Properties and Observables

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

In JavaFX, the Properties API allows developers to bind values to UI controls. This capability is surprisingly easy, but when object models use properties too often, an application can quickly run out of memory.

Carl Dea user avatar by
Carl Dea
·
Apr. 08, 16 · Tutorial
Like (3)
Save
Tweet
Share
17.13K Views

Join the DZone community and get the full member experience.

Join For Free

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 that 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 variables 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 save more memory while 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

This is an interface providing accessor methods to provide a smart determination of the 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 the property name (String) and its 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

This is 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 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 I (or some lucky reader) will create a test comparing all three implementations (the object with all properties, Dirk's, and mine).

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, for most use cases, this might be easier and cleaner code to deal with.

Property (programming) JavaFX Memory (storage engine) Object (computer science)

Published at DZone with permission of Carl Dea. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Web Application Architecture: The Latest Guide
  • Playwright vs. Cypress: The King Is Dead, Long Live the King?
  • DevSecOps Benefits and Challenges
  • Image Classification With DCNNs

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: