Общий ограниченный аргумент несовместим сам с собой

#java #generics

Вопрос:

Я пытаюсь создать универсальный метод, который принимает два типизированных аргумента, один из которых ограничен сам по себе,

 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());
        }
    }
}
 

но компилятор винит в this этом аргумент, потому что

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

Если T это подтип Foo , ясно, что Foo это не наверняка экземпляр T .
Но this быть продолжением Foo , все равно есть Foo .

Принуждение к (T) this актерскому составу, похоже, «»работает»».

Обновить

Я хочу использовать его следующим образом,

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

----

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

Предложение аргумента с подстановкой

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

сбой при использовании с,

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

Комментарии:

1. Предположим, что Бар расширяет Foo. Если есть потребитель, которому конкретно нужен бар (потому что допустимым аргументом является любое значение T, которое расширяет Foo), вы не можете просто передать ему Foo. Это не сработает. Это может зависеть от метода бара, которого у Foo нет. Измените свой двоякий номер на BiConsumer<Foo,... , если вы это имели в виду.

2. Я вообще не вижу причин, по которым вам нужен первый параметр универсального типа.

3. Есть ли причина, по которой вам нужно в первую очередь задействовать «самость»: bar.myself(Optional.of(42), bar::setAnswer); сработало бы, если бы вы использовали Consumer<V> вместо этого. Плюс, конечно, anOptional.ifPresent(bar::setAnswer) работал бы, а не дублировал bar .

4. «Если T это подтип Foo , я не понимаю, почему this (что Foo наверняка) не может быть T «. T Быть подтипом Foo не означает, что Foo это подтип T .

5. @newacct вы совершенно правы, я переформулирую этот вопрос.

Ответ №1:

T extends Foo

Это ограничено Foo , но на самом деле это не обязательно так Foo . Это может быть любой подтип Foo вместо.

Вместо определения переменной типа используйте подстановочный знак:

 final BiConsumer<? super Foo, V> destination
 

Кроме того, лучший способ написать тело метода-это:

 value.ifPresent(consumer);
 

(На самом деле нет большого преимущества в вызове вашего метода по сравнению с тем, чтобы просто делать это напрямую).


Обновление для вашего обновления:

Если вы хотите выразить что-то, напоминающее самотип, вам нужно добавить в класс другую переменную типа:

 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());
        }
    }
 

Затем Bar класс определяется как:

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

Тогда outThere метод работает нормально:

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

Ideone demo

Комментарии:

1.Извините, но я не могу понять, почему до сих пор ни один подтип Foo не Foo является . Может быть, мои старые уроки полиморфизма утеряны.

2. @lucasvc: Да, но это не обязательно полезно — потому что ваш потребитель принимает a T , а не a Foo . Подумайте BiConsumer<String, String> о том, что вы не можете позвонить consumer.accept(new Object(), new Object()) по этому поводу, даже если String это продлится Object . Это то, что вы фактически получили здесь, где T эквивалентно String в этом примере и Foo эквивалентно Object .

3. @lucasvc Предположим, что двоичный клиент был предоставлен (Bar bar, Integer val) -> bar.thisMethodExistsOnBarButNotFoo(val) . Фу не может удовлетворить это, у него нет такого метода. Все бары-это Фу, но не все , что принимает Бар, может принять Фу.

4. @JonSkeet извините, но моя идея заключается в другом, в отличие от вашего примера BiConsumer<Object, Object> с callign consumer.accept(new String(), new String()) : я пытаюсь объявить родителя , но обращаюсь к нему с любым ребенком .

5. @lucasvc: Но это не то, что говорится в вашем коде. У вас есть T extends Foo , и вы вызываете код изнутри Foo . Так что это нормально T , чтобы быть FooSubclass , чтобы вы передавали родительский тип ( Foo , тип this ) чему-то, что специфично для ребенка (тип параметра BiConsumer<T, V> ). Если бы вы T super Foo это сделали, это было бы нормально (для этого аргумента).

Ответ №2:

Допустим, Foo имеет два подкласса Foo1 и Foo2 , оба не переопределяют myself() метод. Затем:

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

Матчи

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

с V бытием String и T бытием Foo2 , в то время this как это классно Foo1 , поэтому вы не можете передать это Foo2 потребителю.

И это то, что обнаружил компилятор.

Ответ №3:

Проблема в том, что нет никакой гарантии, что ваш T совместим с this .

Возможно , BiConsumer это относится к чему-то, что extends T тогда T не вписывалось бы. Проблема в том, что вы делаете выводы T , и это может быть несовместимо с this .

Если вы действительно этого хотите, то вам следует удалить T все вместе и просто использовать Foo .

Если вам нужно что-то расширяющееся Foo и вы хотите сделать вывод об этом, то вы могли бы использовать super вместо этого.

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

Однако вы обнаружите некоторые проблемы с этим подходом.

В противном случае вы также можете использовать Foo напрямую, как уже упоминалось:

 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());
        }
    }
}
 

В противном случае, если вы действительно уверены, что могли бы разыграть его, но на самом деле это не рекомендуется.