Reputation: 3178
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?
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
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
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