Reputation: 12879
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
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
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