Нижний ограниченный подстановочный знак не проверен параметром верхнего ограниченного типа

Интересно, почему эта часть кода успешно компилируется?

Исходный код:

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