#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);
}
Комментарии:
1.Извините, но я не могу понять, почему до сих пор ни один подтип
Foo
неFoo
является . Может быть, мои старые уроки полиморфизма утеряны.2. @lucasvc: Да, но это не обязательно полезно — потому что ваш потребитель принимает a
T
, а не aFoo
. Подумайте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>
с callignconsumer.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());
}
}
}
В противном случае, если вы действительно уверены, что могли бы разыграть его, но на самом деле это не рекомендуется.