Параметр типа явного метода игнорируется для необработанного типа класса; ошибка компилятора?

Я получаю сообщение об ошибке компилятора, вызывающее универсальный метод с явными параметрами типа, как если бы параметр явного типа не был принят во внимание. Минимальный пример:

class CastExample {
    static class ThingProducer<S> {
        public <T> T getThing() { return null; }
    }

    static class ThingA {}

    public static void main(String... args) {
        ThingProducer thingProducer = new ThingProducer();
        ThingA thingA = thingProducer.<ThingA>getThing(); // compile error here
    }
}

ThingProducer — это необработанный тип, поскольку у класса есть параметр типа, но при вызове getThing мы не ссылаемся на параметр типа class, а вместо этого предоставляем параметр типа method. Согласно моему пониманию JLS, это должно быть законным, но это дает мне эту ошибку:

incompatible types: Object cannot be converted to ThingA

Ошибка исчезает, если я

  • удалить <S> из ThingProducer
  • или сделать getThing статичным
  • объявить thingProducer ThingProducer<?> вместо необработанного типа ThingProducer

Это ошибка компилятора? Если нет, то какое правило в JLS определяет такое поведение?


person Andrew Spencer    schedule 16.11.2015    source источник
comment
ThingProducer thingProducer в вашем main - это необработанный тип. Плохо плохо плохо. Например, с ThingProducer<?> thingProducer ваш код компилируется нормально.   -  person Tunaki    schedule 16.11.2015
comment
Верно, но он все равно должен компилироваться как необработанный тип - в случае, если я не ссылаюсь на этот параметр типа.   -  person Andrew Spencer    schedule 16.11.2015
comment
Где вопрос, какой это дубликат? Я искал, прежде чем спрашивать.   -  person Andrew Spencer    schedule 16.11.2015
comment
Ссылка находится вверху этого вопроса.   -  person Tunaki    schedule 16.11.2015
comment
Это не дубликат этого вопроса. Отредактированный вопрос для уточнения.   -  person Andrew Spencer    schedule 16.11.2015
comment
Это вопрос о необработанных типах. Смысл вопроса об обмане состоит в том, чтобы показать, что никогда, никогда не следует использовать необработанные типы, снова прочитайте принятый ответ.   -  person Tunaki    schedule 16.11.2015
comment
Давайте продолжим это обсуждение в чате.   -  person Andrew Spencer    schedule 16.11.2015
comment
Вы не можете выполнить общий вызов необработанного типа. Так что вы уже правильно поняли, аргументы явного типа не учитывались. Потому что вы вызываете метод для необработанного типа. Об этом больше нечего сказать, не используйте необработанные типы.   -  person Holger    schedule 16.11.2015
comment
Я думаю, вы не читали пример кода. У класса действительно есть параметр типа, однако мы не ссылаемся на параметр типа class, а вместо этого предоставляем параметр типа method. Насколько я понимаю JLS, это должно быть законным.   -  person Andrew Spencer    schedule 16.11.2015
comment
... и если это незаконно, все равно стоило бы задокументировать ответ в качестве пограничного случая, поскольку метод компилятора совсем не явный.   -  person Andrew Spencer    schedule 16.11.2015
comment
Только после получения ответа на этот вопрос мне удалось найти вопросы, дубликаты которых: stackoverflow.com/questions/18001550/ и stackoverflow.com/questions/27314649/   -  person Andrew Spencer    schedule 17.11.2015


Ответы (2)


Раздел 4.8 Спецификации языка Java отвечает на ваш вопрос:

Тип конструктора (§8.8), метода экземпляра (§8.4, §9.4) или нестатического поля (§8.3) необработанного типа C, который не унаследован от его суперклассов или суперинтерфейсов, является необработанным типом, который соответствует стирание его типа в общем объявлении, соответствующем C.

В вашем примере getThing() - это "метод экземпляра... необработанного типа C [в данном случае ThingProducer], который не наследуется". Согласно JLS, его тип — это «необработанный тип, который соответствует стиранию его типа в общем объявлении». В универсальном объявлении getThing() его тип T не ограничен, что означает его стирание java.lang.Object.

Обратите внимание, что в спецификации не говорится, что тип getThing() является типом, созданным путем стирания необработанного типа, членом которого он является (то есть ThingProducer). На самом деле это стирание самого getThing(), которое означает, что оба параметра типа (T и S) стираются.

[Кроме того: в своем первоначальном ответе я процитировал другое предложение спецификации: «Ошибка времени компиляции заключается в передаче аргументов типа члену нестатического типа необработанного типа, который не унаследован от его суперклассов или суперинтерфейсов». Мое первоначальное прочтение этого предложения заключалось в том, что компилятор требуется для выдачи ошибки времени компиляции для приведенного выше синтаксиса, поскольку я пришел к выводу, что вы пытаетесь «передать аргументы типа члену нестатического типа сырого типа». Но я передумал: мне кажется, что последнее предложение относится к нестатическому члену типа (то есть к вложенному типу), а не просто к не- статический общий член.]

Конечно, обсуждение раздела 4.8 не будет полным без цитаты из спецификации:

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

person Daniel Pryden    schedule 17.11.2015
comment
Выделенное жирным шрифтом предложение не применяется. Ошибка компиляции исчезает, если thingA объявлен как тип Object, хотя параметры типа все еще передаются: Object thingA = thingProducer.<ThingA>getThing(); Ошибка также исчезает, если <T> изменяется на <T extends ThingA>. Так что ошибка не в самом вызове метода самом, а в последующем приведении/присваивании, как будто тип метода стирается. - person Boann; 17.11.2015
comment
@Боанн: Согласен. Вот о чем было мое редактирование (см. немного о том, что я передумал). Часть, выделенная жирным шрифтом, относится к членам type (то есть к вложенному классу), а не к обычным членам (например, к методу здесь). Тем не менее, я не думаю, что неправильно включить это в мой ответ, а вы? - person Daniel Pryden; 18.11.2015
comment
@AndrewSpencer: Действительно можно было бы ожидать - как вы и ожидали - но это неправда, и никогда не было. :-) Как указано в комментарии к этой ошибке, слишком многого ожидать, чтобы иметь возможность использовать необработанные типы (Map), сохраняя при этом некоторую безопасность типов дженериков (Set<Map.Entry>). Правильно (как вы отметили в своем ответе ниже) использовать подстановочный знак типа (ThingProducer<?>), а не необработанный тип. - person Daniel Pryden; 18.11.2015
comment
@DanielPryden Да, я только что нашел этот комментарий, и действительно, когда он так сформулирован, это вполне разумно. - person Andrew Spencer; 18.11.2015
comment
И @Boann спасибо за то, что вы уладили вопрос - person Andrew Spencer; 18.11.2015

В дополнение к принятому ответу, если вы просто хотите как можно проще исправить ошибку компиляции и не используете параметр типа класса <S>, наиболее подходящим исправлением (спасибо @Tunaki) является

ThingProducer<?> thingProducer = new ThingProducer();

вместо

ThingProducer thingProducer = new ThingProducer();

который удерживает нас в мире дженериков, документируя при этом, что не имеет значения, какой параметр типа.

(В этом урезанном примере было бы разумнее изменить ThingProducer, но я подозреваю, что в реальном мире любой, кто получает эту ошибку, вероятно, имеет дело с устаревшим классом, который они не могут изменить — по крайней мере, это было в моем случае.)

person Andrew Spencer    schedule 17.11.2015