1

Regarding generics, I have been told that casting is bad, and there is usually some way to eliminate casting entirely, for the compiler to do its best work when checking my program. Adding a ChangeListener here to a ReadOnlyDoubleProperty seems to defeat that ideal. Is this a flaw in the API, or an exception to the rule, or is there actually some way to make this code look good?

nodeA.heightProperty().addListener(new ChangeListener<Number>() {
    @Override
    public void changed(ObservableValue< ? extends Number > observable,
                        Number oldValue,
                        Number newValue ) {
        final double ov = oldValue.doubleValue();
        final double nv = newValue.doubleValue();
        @SuppressWarnings("unchecked")
        final ObservableValue<Double> ob = ( ObservableValue< Double > ) observable;
        // do stuff
    }
});

First, the most specific type parameter I can use on the ChangeListener is Number. I should be able to use Double! It's a DoubleExpression! Due to that issue, I have to unpack the arguments to the changed method. Please help pare down the number of lines here. Lambdas could reduce the number of lines, but I'm asking specifically about the generics.

Travis Well
  • 947
  • 10
  • 32
  • 1
    Possible duplicate of [Why IntegerProperty implements Property and not Property?](http://stackoverflow.com/questions/34675004/why-integerproperty-implements-propertynumber-and-not-propertyinteger) – fabian Jan 12 '16 at 09:57

1 Answers1

5

tl;dr: Use asObject.

First note that in the example you posted, there is no reason at all to reference the observable parameter, since it necessarily must be the same reference as nodeA.heightProperty(). So you can simply eliminate the downcast with

nodeA.heightProperty().addListener(new ChangeListener<Number>() {
    @Override
    public void changed(ObservableValue< ? extends Number > observable,
                        Number oldValue,
                        Number newValue ) {
        final double ov = oldValue.doubleValue();
        final double nv = newValue.doubleValue();

        // do stuff with nodeA.heightProperty() ...
    }
});

This isn't at all unusual: in fact I don't think I've ever had a need to reference the observable passed to this method as I always use this same pattern.

As for the fact that ReadOnlyDoubleProperty implements ObservableValue<Number>, as not ObservableValue<Double>, that is either a convenience feature, or a design flaw, depending on your point of view. (See the discussion here, and elsewhere...). The workaround is to call asObject() on the ReadOnlyDoubleProperty, which returns a ReadOnlyObjectProperty<Double> that is bidirectionally bound to the ReadOnlyDoubleProperty, and implements ObservableValue<Double>:

nodeA.heightProperty().asObject().addListener(new ChangeListener<Double>() {
    @Override
    public void changed(ObservableValue< ? extends Double > observable,
                        Double oldValue,
                        Double newValue ) {
        final double ov = oldValue.doubleValue();
        final double nv = newValue.doubleValue();

        // do stuff
    }
});

(Unboxing also makes ov and nv redundant in the above.)

And you can of course make this a lot nicer with lambdas:

nodeA.heightProperty().asObject().addListener((obs, oldValue, newValue) -> {
    // obs is now an ObservableValue<Double>, if you need it
    // oldValue is a Double, which can be treated as a double via unboxing
    // similarly newValue is a Double

    // do stuff..
});
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks! I missed asObject. A reminder to always check the version of the documentation I'm looking at. I would quibble with your assertion that there is no reason to reference the observable parameter. In the code shown, yes you're right. More generally, the listener is not always in the same scope. Anyway, thanks for the excellent answer. – Travis Well Jan 12 '16 at 18:36