2

(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?

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Stewart A
  • 193
  • 1
  • 8

1 Answers1

2

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:

  1. Change the signature to notifyChange(ChangeHolder<? extends T> change) so that the method works for subtypes.
  2. Get rid of the wildcards everywhere, so that you have just <T> instead.

I prefer solution 1, as it is a good idea for signatures to be as general as possible.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • Thanks for the rapid response. Looking at your suggestion #2 first, unless I'm mistaken getting rid of the wildcards means that the type of my Observer instances would need to exactly match types, so I wouldn't be able to attach an `Observer` to an `Observable`. – Stewart A Mar 25 '16 at 17:41
  • @StrangelyTyped That's true. It has to be 1. in that case. You can also change `change` to `public void change(ChangeHolder extends T> changes)`. – Paul Boddington Mar 25 '16 at 17:43
  • Apologies, new to SO, accidentally submitted comment early, using your suggestion #1 I can't attach an `Observer` to an `Observable` because Object won't satisfy `? extends T`, and using `? super T` yields `notifyChange(ChangeHolder super capture#2-of ? super T>) is not applicable for ChangeHolder` – Stewart A Mar 25 '16 at 17:46
  • @StrangelyTyped Ok, give me time to have a think. – Paul Boddington Mar 25 '16 at 17:47
  • `Object` doesn't have to satisfy `? extends T`. `Tree` has to satisfy `? extends Object` (and it does). – Erick G. Hagstrom Mar 25 '16 at 17:51
  • @paul-boddington wait, my bad, `? extends T` does work, I'd neglected to adjust my notifyChange implementation in the test harness which needs to be `? extends Object` as well instead of just Object, thanks for your help – Stewart A Mar 25 '16 at 17:53
  • No problem. I'm glad I could help. – Paul Boddington Mar 25 '16 at 17:58
  • Is there a particular reason that Polymorphism doesn't apply to the generic types used here? (I did have more to say but yet again submitted the comment early accidentally) – Stewart A Mar 25 '16 at 18:04
  • @StrangelyTyped This link is the best you'll find on that question. http://stackoverflow.com/q/2745265/3973077 – Paul Boddington Mar 25 '16 at 18:05