Подстановочные знаки Java по умолчанию равны java.lang.Object вместо верхней границы
Учитывая код
abstract class Base<Thistype extends Base>
{
public void addOnSomethingListener(Consumer<? super Thistype> action)
{}
public void foo()
{
System.out.println("foo");
}
}
class Simple<Thistype extends Simple> extends Base<Thistype>
{
}
class Test1 extends Simple<Test1>
{
}
class Test2 extends Simple
{
}
class Test
{
public static void main(String[] args)
{
Test1 test1 = new Test1();
Test2 test2 = new Test2();
test1.addOnSomethingListener(test ->
{
test.foo(); // VALID as "test" is of type "Thistype" which is "Test1".
});
test2.addOnSomethingListener(test ->
{
test.foo(); // INVALID as "test" is of type "Thistype" which is "java.lang.Object" instead of "Base" which is the upper bound.
});
}
}
Почему универсальный тип класса Test2
не по умолчанию для класса Base
но вместо этого по умолчанию класс java.lang.Object
?
Я предоставил верхнюю границу, но, похоже, это не имеет значения, если используются подстановочные знаки или когда вообще не указывается универсальный символ.
Код в основной функции должен быть скомпилирован, если вы спросите меня.
2 ответа
Объявив Test2
класс без указания аргумента типа:
class Test2 extends Simple {}
вы создаете необработанный тип. Поэтому компилятор рассматривает это как экземпляр Object
,
Это не "по умолчанию Object
".
На самом деле произошло то, что с помощью необработанного типа (Test2
расширяет сырой тип Simple
), он "отключает" все дженерики для этого типа. Так Test2
"s addOnSomethingListener
метод (который унаследован от необработанного типа Simple
) на самом деле имеет подпись void addOnSomethingListener(Consumer action)
(т.е. тип параметра стирается в необработанный тип Consumer
).
Так уж получилось, что сырой тип Consumer
"s accept
метод имеет подпись void accept(Object)
(потому что это нормально void accept(T)
, но T
в Consumer
неограничен, поэтому при использовании необработанного типа Consumer
стирается void accept(Object)
,