lucasvc
lucasvc

Reputation: 838

Generic bounded argument is incompatible with itself

I'm trying to create a generic method that accepts two typed arguments, one of them bounded by itself,

class Foo
{
    <T extends Foo, V> void myself(final Optional<V> value, final BiConsumer<T, V> destination)
    {
        if (value.isPresent())
        {
            destination.accept(~~this~~, value.get());
        }
    }
}

but compiler blames on the this argument, because

error: incompatible types: Foo cannot be converted to T
            destination.accept(this, value.get());
                               ^
  where T,V are type-variables:
    T extends Foo declared in method <T,V>myself(Optional<V>,BiConsumer<T,V>)
    V extends Object declared in method <T,V>myself(Optional<V>,BiConsumer<T,V>)

If T is a subtype of Foo, is clear that Foo is not for sure an instance of T. But this being an extended of Foo, still is Foo.

Forcing the (T) this cast seems to ""work"".

Update

I want to use it the following way,

class Bar extends Foo
{
    void setAnswer(Integer toLife)
    {
    }
}

----

void outThere(Bar bar)
{
    bar.myself(Optional.of(42), Bar::setAnswer);
}

The proposal of wildcarded argument

class Foo
{
    <V> void myself(final Optional<V> value, final BiConsumer<? super Foo, V> destination)
    {
        if (value.isPresent())
        {
            destination.accept(this, value.get());
        }
    }
}

fails on the usage with,

error: incompatible types: invalid method reference
        bar.myself(Optional.of(42), Bar::setAnswer);
                                    ^
    method setAnswer in class Bar cannot be applied to given types
      required: Integer
      found: Foo,V
      reason: actual and formal argument lists differ in length
  where V is a type-variable:
    V extends Object declared in method <V>myself(Optional<V>,BiConsumer<? super Foo,V>)

Upvotes: 1

Views: 168

Answers (3)

Andy Turner
Andy Turner

Reputation: 140319

T extends Foo

It's bounded by Foo, but it isn't necessarily actually Foo. It could be any subtype of Foo instead.

Instead of defining a type variable, use a wildcard:

final BiConsumer<? super Foo, V> destination

Also, a better way to write the method body is:

value.ifPresent(consumer);

(There isn't really much advantage in invoking your method over just doing this directly).


Update for your update:

If you want to express something resembling a self type, you need to add another type variable to the class:

class Foo<F extends Foo<F>>
{
    <V> void myself(final Optional<V> value, final BiConsumer<? super F, V> destination) {
        if (value.isPresent())
        {
            // (F) is an unchecked cast, but is necessary, because
            // nothing constrains F to actually be "itself".
            destination.accept((F) this, value.get());
        }
    }

Then the Bar class is defined as:

class Bar extends Foo<Bar> {
   void setAnswer(Integer toLife) { /* ... */ }
}

Then the outThere method works fine:

void outThere(Bar bar)
{
    bar.myself(Optional.of(42), Bar::setAnswer);
}

Ideone demo

Upvotes: 5

mjs
mjs

Reputation: 22357

The problem is that there is no guarantee that your T is compatible with this.

It could that the BiConsumer is referring to a something that extends T, then T would not fit in. The issue is that you are inferring T and that might not be compatible with this.

If you really want this, then you should remove T all together and just use Foo.

If you want anything that extends Foo and wishes to infer that, then you could use super instead.

<V> void myself(final Optional<V> value, final BiConsumer<? super Foo, V> destination) {
    if ( value.isPresent() ) {
        destination.accept(this, value.get());
    }
}

You are going to find some issues with this approach though.

Otherwise, you could also use Foo directly as mentioned:

public static class Foo { 
    <T, V> void myself(final Optional<V> value, final BiConsumer<Foo, V> destination) {
        if ( value.isPresent() ) {
            destination.accept(this, value.get());
        }
    }
}

Otherwise, if you are really sure you could cast it, but that is not really recommended.

Upvotes: 0

Ralf Kleberhoff
Ralf Kleberhoff

Reputation: 7290

Let's say, Foo has two subclasses, Foo1 and Foo2, both not overriding the myself() method. Then:

Foo1 me = ...;
Optional<String> value = ...
BiConsumer<Foo2,String> consumer = ...;
me.myself(value, consumer);

matches

<T extends Foo, V> void myself(final Optional<V> value, final BiConsumer<T, V> destination) {...}

with V being String and T being Foo2, while this is of class Foo1, so you can't pass it into a Foo2 consumer.

And that's what the compiler detected.

Upvotes: 2

Related Questions