David David - 3 months ago 8
Java Question

Can I have a list property that accepts subtypes?

In my app I have a certain data item, based on an abstract base datatype, with concrete subtypes of that.

I have a UI component that displays a list of these data items. I want to expose a ListProperty of the base datatype (which has all the common fields I want to display), and be able to bind it to other components that provide lists of the specific subtypes. But I can't seem to get the lists to be compatible.

Code sample that illustrates the problem:

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;

public class GenericTest
{
static class BaseClass {}
static class SubClass extends BaseClass {}

ListProperty<SubClass> sourceList = new SimpleListProperty<>();
ListProperty<? extends BaseClass> observingList = new SimpleListProperty<>();

{
observingList.bind( sourceList );
}

}


I get a compilation error on the bind() call - incompatible types. I thought I understood Java generics, but obviously not.

Is it possible to bind list properties with generics in the mix, or should I look for an alternative solution?

Answer

The element type of observingList is "some unknown subclass of BaseClass". So essentially you would have to bind to a list whose element type is compatible with that specific, but unknown, type.

From the compiler's perspective, you could have assigned observingList to be, e.g. a different subclass of BaseClass, i.e. if you had

static class AnotherClass extends BaseClass {}

and then

observingList = new SimpleListProperty<AnotherClass>();

would be perfectly legal.

Now since instances of SubClass are not instances of AnotherClass, it should be clear why the binding cannot compile.

Perhaps another way to see this, is to think about trying to call observingList.add(...). What could you legally pass into that method? While you clearly don't need to call that yourself (because you're binding it to something), the implementation of bind(...) will obviously need to call add(...) at some point: if there's no way for you to call that method, there's no way for the binding to do so either.

Essentially, for the binding to work, you need to specify the types (without wildcards). So, obviously you can do

ListProperty<SubClass> observingList = new SimpleListProperty<>();

and then the binding works, but you could also parametrize the type, e.g.:

public static <T extends BaseClass> ListProperty<T> createBoundList(ListProperty<T> sourceList) {
    ListProperty<T> observingList = new SimpleListProperty<>();
    observingList.bind(sourceList);
    return observingList ;
}

and then

ListProperty<? extends BaseClass> observingList = createBoundList(sourceList);

will compile.

Comments