Reputation: 193
(For the purposes of this post, lets set aside java.util.Observable)
I was experimenting around with generics, and then wildcard types. The aim was to create a type-generic observable cache with deltas provided to the observers. Where this starts to go off the rails is I wanted to allow more generic observers to be used than the one specified in the Observable, e.g. Observer<Object>
or some other common superclass.
I've since concluded that this is overly complex for my use case, but the problem itself continues to bother me since I clearly don't understand how to use type wildcarding properly.
So if we start with a simple observer interface:
public interface Observer<T> {
public void notifyChange(ChangeHolder<T> change);
}
And the associated ChangeHolder, in a full implementation this would be more complex, providing lists of added / updated / deleted objects, but this is sufficient to demonstrate the issue
public interface ChangeHolder<T> {
T getChange();
}
So with the Observer defined, I tried to implement the Observable abstract class:
public abstract class Observable<T> {
private Set<Observer<? super T>> observers = new HashSet<>();
public void addObserver(Observer<? super T> obs){
observers.add(obs);
}
public void change(ChangeHolder<T> changes){
for(Observer<? super T> obs : observers){
obs.notifyChange(changes);
}
}
}
And with that I could define some object caches, by declaring something like class TreeCache extends ObservableCache<Tree>
, (From this point on I'll use Tree as an example class to be used as a T, assume it to be a simple POJO extending only from Object) and pass ChangeHolder<Tree>
objects to TreeCache.change()
when necessary. Unfortunately the compiler disagrees:
The method notifyChange(ChangeHolder<capture#2-of ? super T>) in the type Observer<capture#2-of ? super T> is not applicable for the arguments (ChangeHolder<T>)
Which is where my understanding ends.
Without the ChangeHolder class (if my notifyChange method just took a plain T instead) it works just fine since it's perfectly legal to pass a Tree to Observer.notifyChange(Object).
I inferred that I should be able to do the same with the ChangeHolder - ChangeHolder<T>
should satisfy notifyChange(ChangeHolder<? super T>)
in the same way that T
satisfies notifyChange(? super T)
but clearly I am misunderstanding something?
Upvotes: 2
Views: 83
Reputation: 37655
There is no wildcard in the signature notifyChange(ChangeHolder<T> change)
. Therefore the generic type of the passed argument must exactly match the generic type of the Observer
instance.
Observer<? super T>
means an Observer
of some unknown type that is a supertype of T
. Since the generic type of obs
may not exactly match the generic type of changes
, the notifyChange
method is not applicable.
There are two possible fixes:
notifyChange(ChangeHolder<? extends T> change)
so that the method works for subtypes.<T>
instead.I prefer solution 1, as it is a good idea for signatures to be as general as possible.
Upvotes: 2