Home > database >  Generic bounded argument is incompatible with itself
Generic bounded argument is incompatible with itself

Time:10-12

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, I don't understand why this (which is a Foo for sure) cannot be a T.

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>)

CodePudding user response:

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

CodePudding user response:

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.

CodePudding user response:

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.

  • Related