If I have a generic <T2 extends T1>
, does the compiler infer T1 super T2
?
I have a more complex collection, which I reduced to the MWE below. The collection shall be mergeable with any such collection with elements of any subtype.
Now I'm wondering, why the call to forEach
in merge
is not accepted. It fails with
java.util.function.Consumer<java.util.Optional> cannot be converted to java.util.function.Consumer<java.util.Optional<? super T2>>
I've depicted the type relationships in the diagram below. In merge
, T2
extends T1
. The call to forEach
is applied on the other
object, hence the T1
of this
becomes the ?
of other.forEach
and the T2
of this.merge
is the T1
of other
. Hence, this
' T1
should be accepted as super of other
's T1
.
I also tried public void merge(Group<? extends T> other)
with the same result. And public void merge(Group<T> other)
does not accept such collections with elements of any subtype of T1
.
MWE:
class Group<T1> {
public <T2 extends T1> void merge(Group<T2> other) {
Consumer<Optional<T1>> task = t -> t.ifPresentOrElse(this::foo, this::bar);
other.forEach(task);
}
public Collection<Optional<T1>> values() {
return List.of();
}
public void forEach(Consumer<Optional<? super T1>> consumer) {
values().forEach(consumer);
}
private void foo(T1 t) {}
private void bar() {}
}
Relationships:
this.T1 -becomes-> other.forEach.?
^ |
| super
extends |
| v
this.merge.T2 -is-> other.T1
You can solve your problem using
class Group<T1> {
public <T2 extends T1> void merge(Group<T2> other) {
Consumer<Optional<? extends T1>> task=t->t.ifPresentOrElse(this::foo, this::bar);
other.forEach(task);
}
public Collection<Optional<T1>> values() {
return List.of();
}
public void forEach(Consumer<? super Optional<T1>> consumer) {
values().forEach(consumer);
}
private void foo(T1 t) {}
private void bar() {}
}
This is an application of the PECS rule.
Your task
will treat the Optional
as a producer, hence, has to declare Optional<? extends T1>
to allow the optional to produce subtypes of T1
. We know that optional will always act as a producer, but Java’s generic type system does not have the concept of classes always acting like a producer or consumer and needs an explicit ? extends T1
here.
The forEach
receives a consumer, even literally in the interface name, so ? super …
is the right way to declare that this consumer may be a consumer of supertypes of Optional<T1>
. This does not only include Object
, the superclass of Optional
, but also Optional<? extends T1>
when T1 := T2
, as in the invocation within merge
, as Optional<? extends T1>
is a supertype of Optional<T2>
.
Of course, you could also solve the issue by simply using
Consumer<Optional<T2>> task = t -> t.ifPresentOrElse(this::foo, this::bar);
as this only requires foo
to accept a T2
argument, which it always does, as T2
is a subtype of T1
. But changing the signature of forEach
to raise the flexibility is always a good move. Since it’s a public
method, there might be other callers benefitting from it.