Myk Willis
Myk Willis

Reputation: 12879

Passing Observable with specified generic List type as method argument

I can write a method that accepts a List, the elements of which extend a given base class, like this:

class BaseClass { }
class SubClass extends BaseClass { }

static void listMethod(List<? extends BaseClass> list) { }

Calling this method is straightforward, and works as expected:

    List<BaseClass> b = Collections.empty();
    List<SubClass> s = Collections.empty();

    listMethod(b);  // ok
    listMethod(s);  // ok

Now I would like to change my method such that it accepts not a List, but instead an Observable that emits a List. The signature thus changes to be:

static void observableListMethod(Observable<List<? extends BaseClass>> listObservable) { }

Unfortunately, attempts to call this new method result in argument mismatch errors of the form:

Observable<List<BaseClass>> cannot be converted to Observable<List<? extends BaseClass>>

Unlike the general issue of dealing with generic collections in Java, the issue here does not seem to be an incorrect assumption casting between subclass and baseclass collections. Rather it seems to have more to do with the specific type of the Observable being used.

    Observable<List<BaseClass>> b = Observable.just(Collections.emptyList());
    Observable<List<SubClass>> s = Observable.just(Collections.emptyList());

    observableListMethod(b);    // argument mismatch / incompatible types
    observableListMethod(s);    // argument mismatch / incompatible types

    // I can map the List to a read-only variant...
    observableListMethod(b.map(Collections::unmodifiableList)); // ok (for readonly)
    observableListMethod(s.map(Collections::unmodifiableList)); // ok (for readonly)

The best solution I've come up with so far is to change the method signature to specify BaseClass specifically (not ? extends BaseClass), and then do some ugly List unpacking and casting using Rx:

static void observableListMethod2(Observable<List<BaseClass>> listObservable) { }
public static void callObservableListMethod2() {
    Observable<List<BaseClass>> b = Observable.just(Collections.emptyList());
    Observable<List<SubClass>> s = Observable.just(Collections.emptyList());

    observableListMethod2(b);    // ok
    observableListMethod2(s.flatMapIterable(it -> it).cast(BaseClass.class).toList()); // ok, but yuck!
}

Surely there is a cleaner way to accomplish what I'm trying to do?

Upvotes: 2

Views: 1614

Answers (2)

Jorn Vernee
Jorn Vernee

Reputation: 33885

As the other answer shows you can solve this by using a type variable. But to explain what's happening;

static void observableListMethod(Observable<List<? extends BaseClass>> listObservable) { }

Requires an Observable<List<? extends BaseClass>> as a parameter. In other words it takes an Observable<T> where T is List<? extends BaseClass>>.

Now, you try to pass a Observable<List<BaseClass>> for instance, i.e. an Observable<U> where U is List<BaseClass>, a different type than T. But since generics are invariant you can not assign an Observable<U> to an Observable<T>. Even if U is assignable to T.

To make it work, you'd need Observable<? extends T> as a parameter. If you then substitute T for List<? extends BaseClass>> again you get:

Observable<? extends List<? extends BaseClass>>

If you make that the parameter type this should compile:

Observable<List<BaseClass>> b = Observable.just(Collections.emptyList());
Observable<List<SubClass>> s = Observable.just(Collections.emptyList());

observableListMethod(b);
observableListMethod(s); 

Upvotes: 3

user3237736
user3237736

Reputation: 857

Seems this could be a workaround:

static <T extends BaseClass> void observableListMethod(Observable<List<T>> listObservable)

Maybe I misunderstand the question, but at least that one is compiling for me.

Upvotes: 0

Related Questions