3

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
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
ktul
  • 183
  • 9
  • 1
    To make the main problem obvious: `List>` is not compatible with `List>`. You may want to try `Consumer super Optional>`, with `task` being declared as `Consumer> task`. There's no reason to make `task` a `Consumer>` – ernest_k Jan 31 '22 at 19:51
  • The List example makes sense, thanks. `Consumer> task` doesn't work as it wouldn't accept `this::forEach` anymore – ktul Jan 31 '22 at 23:32
  • And `Consumer super Optional>` doesn't help as `Optional` is a final class – ktul Jan 31 '22 at 23:39

2 Answers2

2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • This works. Interestingly, your second version and `forEach` kept flexible with super works as well, i.e., `Consumer> task` is also accepted by `forEach(Consumer>)` – ktul Jun 15 '22 at 18:02
1

If you have a Consumer<Optional<? super T1>> consumer then you can pass it (to the accept(T) method) an Optional<Object> because Object is a supertype of T1.

On the other hand, you cannot pass an Optional<Object> to Consumer<Optional<T1>> task because T1 might not be Object.

So you cannot assign the Consumer<Optional<T1>> task to the Consumer<Optional<? super T1>> consumer to prevent your consumer from being passed objects of types it does not expect.

Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49