khatchad
khatchad

Reputation: 3178

Can't resolve this Java generics compile-time warning

Please consider the following code:

public abstract class Subject {
    private Collection<Observer> observerCollection = new HashSet<>();
    // ...
    protected void notifyObservers() {
        this.observerCollection.stream().filter(Objects::nonNull).forEach(o -> o.update(this));
    }
}

public interface Observer<T extends Subject> {
    void update(T subject);
}

I am getting the following compile-time warnings:

Observer is a raw type. References to generic type Observer should be parameterized

Type safety: The method update(Subject) belongs to the raw type Observer. References to generic type Observer should be parameterized

One comes at the call to update and for the life of me I can't figure out how to resolve it without using the warning suppressions. I've tried several ways to resolve the warning without luck. Any ideas on how this can be resolved?

Motivation

Consider the following client code:

public class IntegerContainer extends Subject {
    private int integer;
    
    public IntegerContainer(int integer) {
        this.integer = integer;
    }

    public int getInteger() {
        return this.integer;
    } // ...
}

public class IntegerObserver implements Observer<IntegerContainer> {
    private int cachedInteger;

    @Override
    public void update(IntegerContainer subject) {
        this.cachedInteger = subject.getInteger(); // avoid cast here.
    } // ...
}

The motivation for using generics in the Observer is to avoid a cast of the subject parameter so that the observer can retrieve the state of the subject.

Upvotes: 2

Views: 533

Answers (2)

Tamas Rev
Tamas Rev

Reputation: 7166

I'd focus on the value being passed between the Subject and Observer. I.e. both classes have one type parameter and the related methods make sure that the types are compatible:

public interface Observer<T> {
    void update(T value); // no subject, but a value
}

public class Subject<T> {
      private Collection<Observer<? super T>> observers = new HashSet<>();

      protected void notifyObservers() {
        this.observers.stream().filter(Objects::nonNull).forEach(o -> o.update(this.getValue()));
      }

      public void addObserver(Observer<T> observer) { // adding the right kind of observer
          observers.add(observer);
      }

      abstract public T getValue(); // returning the value - this one is abstract
}

The key above is the abstract public T getValue(); method. Here is how you can write an IntegerContainer and and IntegerObserver :

public class IntegerContainer extends Subject<Integer> {

    private int value;

    public IntegerContainer(int value) {
        this.value = value;
    }

    @Override
    public Integer getValue() {
        return value; // this is the parameter of the update() call
        // you could even compute here something
        // you can pass entire objects too, if required
    }

}

public class IntegerObserver implements Observer<Integer> {
    private int cachedInteger;

    @Override
    public void update(Integer value) {
        this.cachedInteger = value; // no cast here
    } // ...
}

You can put them together like this:

IntegerContainer container = new IntegerContainer(3);
IntegerObserver observer = new IntegerObserver();
container.addObserver(observer);
container.notifyObservers();

Upvotes: 0

Louis Wasserman
Louis Wasserman

Reputation: 198083

This doesn't have anything to do with streams; it just straight up won't work.

An Observer<? extends Subject> is more or less unusable, because you don't know what subtype of Subject it's an observer of. For all you know, observerCollection only contains an Observer<SomeSubtypeOfSubjectThatNobodyEvenHeardOf>. (See the PECS principle; Observer is a consumer.)

I don't think there's any type-safe way to do this cleanly, frankly, because you can't say in Subject that the attached observers all accept this subtype of Subject, because there's no way to refer to "this subtype of Subject." The closest hack I can think of is

abstract class Subject<T extends Subject<T>> {
  private Collection<Observer<? super T>> observers;
  protected void notifyObservers() {
    this.observerCollection.stream().filter(Objects::nonNull).forEach(o -> o.update((T) this)); // yes, this cast is unchecked
  }
}

class SubSubject extends Subject<SubSubject> {
  ...
}

Upvotes: 2

Related Questions