Changes detected by reference equality for all ObservableValues... except String

John Hendrikx john.hendrikx at gmail.com
Tue Mar 21 11:26:22 UTC 2023


I had a run in today with a (known) issue where StringProperty seems to 
violate the implementation spec as described by ObservableValue for 
which I'd really like to investigate a solution:

  * All implementing classes in the JavaFX library check for a change 
using reference
  * equality (and not object equality, {@code Object#equals(Object)}) of 
the value.

Yet, in StringPropertyBase#set, we see this:

         if ((value == null)? newValue != null : !value.equals(newValue)) {
             value = newValue;
             markInvalid();
         }

Note the use of `equals` here.  In ObjectPropertyBase (and all the other 
PropertyBase classes, even something like ListPropertyBase) it's this:

         if (value != newValue) {
             value = newValue;
             markInvalid();
         }

Now, I can understand why this is done, as String is almost regarded as 
a primitive class in Java, however, it does make downstream properties 
which may not know they're dealing with Strings and that need to check 
for changes a bit harder to implement. A downstream property may want to 
check for changes to prevent an unnecessary invalidation for example.

This could look like this:

         if (isValid() && source.getValue() != getValue()) {
             invalidate();
         }

Or this:

             T value = source.getValue();

             if (cachedValue != value) {
                 cachedValue = value;
                 invalidate();
             }

Now, since StringProperty strictly speaking violates the implementation 
specification for JavaFX provided properties, but not the general 
contract of an ObservableValue, there are I think two possible actions:

1) Make StringProperty no longer violate this implementation 
specification and make it use reference equality; this can mean 
invalidations and changes are triggered where, according to equals, the 
old and new value are the same.

2) Make an exception for String values, as they're almost like 
primitives in Java, and update the implementation specification

In the first case, downstream properties can continue to use reference 
equality to do their checks.  In the second case, we may need to change 
these to specifically use `equals` when a String value is encountered.  
Strictly speaking that would mean changing ObjectPropertyBase as well to 
change its equality:

Old:

         if (value != newValue) {
             value = newValue;
             markInvalid();
         }

New:

         boolean changed = value instanceof String s ? 
!s.equals(newValue) : value != newValue;

         if (changed) {
             value = newValue;
             markInvalid();
         }

This way of checking equality would best be encoded somewhere that is 
easy to reach for other code that needs to do the same, so I'd propose a 
static method on ObservableValue (or somewhere else):

     static <T> boolean isEquals(T a, T b) {
       return a instanceof String s ? s.equals(b) : a == b;
     }

I'd like to hear your thoughts on this as it is can have consequences 
for how the `when` fluent binding prevents invalidations, but also other 
(future) implementations that sometimes deal with Strings, and sometimes 
with any other kind of object.

--John



More information about the openjfx-dev mailing list