Нижний ограниченный подстановочный знак не проверен параметром верхнего ограниченного типа
Интересно, почему эта часть кода успешно компилируется?
Исходный код:
abstract class A<K extends Number>
{
public abstract <M> A<? super M> useMe(A<? super M> k);
}
Скомпилировано успешно
Как это работает и почему это компилируется? M - это любой тип, так почему его можно использовать?. Должно ли быть: <M extends Number>
?
Это не скомпилируется:
abstract class A<K extends Number>
{
public abstract <M> A<? super M> useMe(A<M> k);
}
Сообщение об ошибке:
аргумент типа M не находится в пределах границ переменной типа K, где M, K являются переменными типа:
M extends Объект, объявленный в методе useMe (A)
K extends Число, объявленное в классе A
В чем разница?
Ответы
Ответ 1
Это поведение компилятора обсуждалось на этой ошибке Eclipse. Первоначально компилятор Eclipse делал ошибку для выражения в вашем примере, а javac - нет. Хотя я еще не исследовал JLS напрямую, консенсус, похоже, заключается в том, что в спецификации нет ничего, требующего проверки более низких ограниченных подстановок от ограничений параметров типа. В этой ситуации он в конечном итоге оставил вызывающему абоненту тип, удовлетворяющий ограничениям (как предполагал Стефан Херрманн на этом посту).
Ответ 2
Это удивительно бессмысленный фрагмент кода.
Все это говорит о том, что класс A
принимает общий тип K
, который является Number
, и существует метод useMe
, который возвращает A<T>
с каким-то бессмысленным дополнительным ограничением на T
(кроме, очевидно, ).
Вот реализация, чтобы показать, как мало говорится сахаром:
abstract class A<K extends Number> {
public abstract <M> A<? super M> useMe(A<? super M> k);
}
class B extends A<Number> {
@Override
public <M> A<? super M> useMe(A<? super M> k) {
// Not much more you can do here but this.
return k;
}
}
Материал ? super M
- это просто бессмысленный gobbledegook - весь компилятор может извлечь из него то, что и переданный ему параметр, и возвращаемый результат должны быть суперклассом определенного неназванного класса.
В процессе компиляции легко обнаружить ошибки кодирования. Использование mumbo-jumbo, например, это просто вводящая в заблуждение обфускация.
Ответ 3
Добавление <M extends Number>
в первый пример не добавляет ничего компилятора. Помните, что вы говорите "тип, который является супертипом M", если мы говорим: "M - это подтип числа" и "тип является супертипом M", мы на самом деле не говорим, является ли тип подтипом Число.
Для лучшего примера: M
be Integer
и переменная типа A<Object>
. Хотя очевидно, что это не сработает, оно удовлетворяет всем требованиям функции правильно.
Поскольку не существует способа исправить определение функции, он просто пропускает и предполагает, что сайт вызова поймает проблему.
Ответ 4
На ваш вопрос две части:
Часть 1: Что такое <M>
?
Наличие общего параметра в методе делает его "типизированным методом", что означает, что метод имеет общий тип, который определяется вызывающим, как правило, путем вывода. Это может быть ограничено. Если класс тоже имеет тип и метод является методом экземпляра, эти два типа не связаны между собой.
Часть 2:
Общие типы должны точно совпадать. Причина сводится к тому, что если B
является подтипом A
, SomeClass<T extends B>
не является подтипом SomeClass<T extends A>
, более конкретно, SomeClass<A>
не является подтипом SomeClass<? super A>
.