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

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

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

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

В чем разница?


person Pawel    schedule 23.12.2013    source источник
comment
Вы можете получить лучшие ответы, включив фактическое сообщение об ошибке, которое вы получаете от компилятора. (Нам не нужно запускать IDE, чтобы понять ваш вопрос.)   -  person meriton    schedule 24.12.2013
comment
Какое фактическое сообщение об ошибке? Вопрос в том, почему это компилируется?   -  person Dawood ibn Kareem    schedule 24.12.2013
comment
Примечание для тех, кому любопытно: замена <M> на <M extends Number> в более поздних версиях также позволяет скомпилировать его.   -  person Guvante    schedule 24.12.2013
comment
Похоже, что возвращаемые типы можно заменить на void ради вопроса.   -  person Paul Bellora    schedule 24.12.2013
comment
Я предполагаю, что единственный реальный ответ на этот вопрос заключается в том, что компилятор недостаточно умен, чтобы решить, что он не должен этого допускать.   -  person Dawood ibn Kareem    schedule 24.12.2013
comment
@DavidWallace: он удивлен тем, что сообщение об ошибке, которое он получает из второго примера кода (связанное несоответствие в параметре типа A), не появляется и для первого примера кода, поскольку неизвестно, что M является подтипом Номер в любом случае.   -  person meriton    schedule 24.12.2013
comment
@DavidWallace: я думаю, что это глубже, называя его супертипом, вы не можете определить его работающим способом, поскольку <? super M> всегда включает object, который в любом случае не может удовлетворить требование.   -  person Guvante    schedule 24.12.2013
comment
Мой ответ будет таким: ваше второе объявление не будет компилироваться, потому что в параметре функции A<M> k: граница типов M на самом деле строго Object во время компиляции, но поскольку ваш класс A<K extends Number> объявлен A, его можно параметризовать только с типом, с которым верхняя граница Number. Однако первый компилируется, потому что использование ? super M как минимум убеждает компилятор в том, что тип может иметь привязку, отличную от Object, и может быть обнаружен во время выполнения.   -  person Sage    schedule 24.12.2013
comment
@Guvante Если вы и я можем посмотреть на эти несколько строк кода и сказать: «Эй, это не должно компилироваться, то компилятор тоже должен это сделать».   -  person Dawood ibn Kareem    schedule 24.12.2013
comment
Я добавил сообщение компилятора. Так это ошибка?   -  person Pawel    schedule 24.12.2013
comment
Павел: Вам нужно прочитать спецификацию, чтобы определить, какие гарантии она дает в этой ситуации. Возможно, они косвенно включили примечание об этом поведении. @DavidWallace: Спецификация настолько полна, насколько это возможно, и сайт вызова может завершить контракт, поэтому, компилируется он или нет, зависит от особенностей определения языка.   -  person Guvante    schedule 24.12.2013


Ответы (4)


Такое поведение компилятора обсуждалось в этой ошибке Eclipse. Первоначально компилятор Eclipse сделал ошибку для выражения в вашем примере, а javac - нет. Хотя я еще не искал JLS напрямую, похоже, что консенсус заключается в том, что в спецификации нет ничего, требующего проверки подстановочных знаков с нижней границей на соответствие границам параметров типа. В этой ситуации в конечном итоге вызывающая сторона должна назначить тип, который удовлетворяет ограничениям (как предположил Стефан Херрманн в этом посте).

person Paul Bellora    schedule 24.12.2013
comment
вы уверены, что javac не будет! Мой NetBeans также продолжает сообщать об этом исключении. - person Sage; 24.12.2013
comment
@Sage Он компилируется для меня в Eclipse (1.7) и javac (sun-jdk 1.7) Ideone: ideone.com/lRb23T. Какой компилятор установлен в вашем Netbeans? - person Paul Bellora; 24.12.2013
comment
@Sage У меня есть NetBeans 7.4, и первый пример тоже компилируется для меня. JDK 1.7. - person Pawel; 24.12.2013
comment
@Pawel, да, но вопрос по второму примеру и отличию от первого с пометкой *why?* :) ;) - person Sage; 24.12.2013
comment
@Sage На самом деле вопрос в том, почему первый пример компилируется. - person Paul Bellora; 24.12.2013
comment
@PaulBellora, да, ты тоже прав. На самом деле я вижу вопрос как запрос: причина, по которой первый компилируется, а второй нет, с обеих сторон, и это то, что я хотел иметь в виду. Я думаю, что javaC работает, поскольку в нем указано поведение стирания: как я уже упоминал в комментарии к панели вопросов OP: если параметр T не ограничен, компилятор Java заменяет его на Object. Следовательно, он превышает границу Number, которая является верхней границей типа параметра A, хотя я могу ошибаться :) - person Sage; 24.12.2013
comment
@Sage Мне пришлось бы не согласиться с этой теорией, поскольку стирание происходит только после всех проверок времени компиляции. Я думаю, что это просто случай практичности JLS, поскольку он не требует слишком сложных проверок для типов, которым в любом случае будут проверяться назначения. - person Paul Bellora; 24.12.2013
comment
Я их понимаю, почему можно оставить нижнюю границу и об этом должен заботиться программист. Но верхняя граница может удовлетворять требованиям, как и нижняя граница. - person Pawel; 25.12.2013

Это удивительно бессмысленный фрагмент кода.

Все, что он говорит, это то, что класс A принимает общий тип K, который является Number, и есть метод useMe, который возвращает A<T> с некоторыми бессмысленными дополнительными ограничениями на T (кроме Number, очевидно).

Вот реализация, показывающая, как мало говорит сахар:

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 просто бессмысленная абракадабра - все, что компилятор может извлечь из него, это то, что и переданный ему параметр, и возвращаемый результат должны быть суперклассом определенного безымянного класса.

Обобщения предназначены для облегчения обнаружения ошибок кодирования во время компиляции. Использование такой чепухи просто вводит в заблуждение.

person OldCurmudgeon    schedule 24.12.2013

Добавление <M extends Number> к первому примеру не добавляет ничего важного для компилятора. Помните, что вы говорите «тип, который является супертипом M», если мы говорим «M является подтипом Number» и «тип является супертипом M», мы на самом деле не говорим, является ли тип подтипом M. Количество.

Для лучшего примера пусть M будет Integer, а переменная будет типа A<Object>. Хотя очевидно, что это не сработает, оно правильно удовлетворяет всем требованиям функции.

Поскольку не существует способа исправить определение функции, он просто пропускает его и предполагает, что сайт вызова обнаружит проблему.

person Guvante    schedule 23.12.2013
comment
Точно, оба разные, так почему это компилируется? Я знаю значение символов. - person Pawel; 24.12.2013
comment
@SotiriosDelimanolis: Извините, вы правы, мой первый ответ не был полным, я еще немного подумал и придумал лучший. - person Guvante; 24.12.2013

В вашем вопросе две части:

Часть 1: Что такое <M>?

Наличие универсального параметра в методе делает его «типизированным методом», что означает, что метод имеет универсальный тип, который определяется вызывающей стороной, обычно путем логического вывода. Он может быть ограничен. Если у класса тоже есть тип, а метод является методом экземпляра, эти два типа не связаны.

Часть 2:

Общие типы должны соответствовать точно. Причина сводится к тому, что если B является подтипом A, то SomeClass<T extends B> не является подтипом SomeClass<T extends A>, точнее, SomeClass<A> не является подтипом SomeClass<? super A>.

person Bohemian♦    schedule 24.12.2013