Boilerplate
This interface is NOT USED, it is only here for Documentation.
ObservableValue and Property both have a generic type parameter
, which determines the type for the property's value. However, JVM's generics are 2nd class citizens due to type erasure
.
When we compile :
val a : Property = ...
the generic type Int
is erased, and the actual type is Property<*>
. Most of the time, this isn't a problem, but for Property and ObservableValue it is.
I need to define functions, which operate on string properties. For example, I want to add two Property
fun add( a : Property, b : Property ) = ...
But this won't work, because this function has exactly the same signature as :
fun add( a : Property, b : Property ) = ...
(remember the generic type is erased).
My solution is to create autogenerated boilerplate such as :
interface ObservableInt : Observable
interface ObservableFloat : Observable
interface IntProperty : Property, ObservableInt
interface FloatProperty : Property, ObservableFloat
class SimpleIntProperty(initialValue : Int) : SimpleProperty(initialValue), IntProperty
class SimpleFloatProperty(initialValue : Float) : SimpleProperty(initialValue), FloatProperty
...
There is quite a lot of boilerplate, because there are more than 2 types other than Int
and Float
, and more classes/interfaces other than ObservableValue
, Property
and SimpleProperty
. We also need to duplicate each piece of boilerplate for nullable values (which glok calls Optional
). So we also need :
interface ObservableOptionalInt : Observable
interface OptionalIntProperty : Property, ObservableOptionalInt
class SimpleOptionalIntProperty(initialValue : Int?) : SimpleProperty(initialValue), OptionalIntProperty
...
But at least we can now define addition for ints and floats :
fun add( a : ObservableInt, b : ObservableInt ) : ObservableInt = ...
fun add( a : ObservableFloat, b : ObservableFloat ) : ObservableFloat = ...
Note, we can only use these functions if we have an IntProperty or FloatProperty. If we have Property<Int>
or Property<Float>
we cannot use them.
However, there are more problems...
Consider SpinnerBase
, the base class for IntSpinner
and FloatSpinner
etc. It is ugly, because the valueProperty cannot be defined as :
val valueProperty : Property
without losing the advantages gained by the boilerplate. So we need two generic type parameters. V
for the type held (such as Int
or Float
), and P
, which is the type of the property (such as IntProperty
or FloatProperty
). It's ugly, but works.
Now consider SingleSelectionModel
. It has a selectedItemProperty
, and this time we can't use the same hack, without getting into a real mess. So its type is ObservableValue<T?>
. and our boilerplate isn't being used :-( selectedItemProperty
is a 2nd class citizen. Grr.
It's all a bit of a mess :-(