Неуниверсальная ссылка на универсальный класс приводит к неуниверсальным возвращаемым типам.

У меня есть устаревший класс, в котором сам класс не является универсальным, но один из его методов возвращаемого типа использует дженерики:

public class Thing {
    public Collection<String> getStuff() { ... }
}

getStuff() использует дженерики для возврата набора строк. Поэтому я могу перебирать getStuff(), и нет необходимости приводить элементы к String:

Thing t = new Thing();
for (String s: t.getStuff())  // valid
{ ... }

Однако, если я изменю сам Thing на общий, но оставлю все остальное прежним:

public class Thing<T> {
    public Collection<String> getStuff() { ... }
}

а затем продолжайте использовать неуниверсальную ссылку на Thing, getStuff() больше не возвращает Collection<String> и вместо этого возвращает нетипизированное Collection. Таким образом, клиентский код не компилируется:

Thing t = new Thing();
for (String s: t.getStuff())  // compiler complains that Object can't be cast to String
{ ... }

Почему это? Каковы обходные пути?

Я предполагаю, что, используя неуниверсальную ссылку на универсальный класс, Java отключает все дженерики для всего класса. Это боль, потому что теперь я сломал свой клиентский код, сделав Thing универсальным.

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


person Steve Kuo    schedule 16.01.2009    source источник


Ответы (3)


Хорошо, возьми два, я неправильно понял твой вопрос.

Когда вы делите Thing (это называется необработанный тип) вместо Thing<?> (параметризованный тип), компилятор Java удаляет все общие аргументы, даже если (как в вашем случае) универсальный тип метода не имеет ничего общего с общим типом класса.

Из (отлично) часто задаваемых вопросов по Java Generics:

Могу ли я использовать необработанный тип, как любой другой тип?

Методы или конструкторы необработанного типа имеют сигнатуру, которая будет у них после стирания типа.

Это, казалось бы, безобидное и ненавязчивое предложение описывает рассматриваемое поведение. Вы используете Thing в качестве необработанного типа, поэтому возвращаемый тип — Collection (а не Collection<String>), так как это тип после стирания типа.

Смущенный? Неудивительно. Просто посмотрите на размер этого FAQ. Вероятно, на земле есть около трех человек, которые понимают все значение Java Generics. Просто взгляните на мою любимую декларацию из JDK:

Enum<T extends Enum<T>> 

(Объяснение этому тоже есть в FAQ).

person cletus    schedule 16.01.2009
comment
Я делаю Thing универсальным из-за другого метода (не указанного в моем примере). - person Steve Kuo; 16.01.2009
comment
Если вещь является Вещью (а не Вещью‹?›), она не будет компилироваться. Давай, попробуй. - person Steve Kuo; 16.01.2009
comment
Не понял вопроса, поменял. - person cletus; 16.01.2009
comment
@Jon: все это связано с решением сохранить обратную совместимость (стирание типа). Разработчики Java находят такие вещи, как IEnumerable‹T› и IEnumerable в C#, не имеющие никакого отношения друг к другу, немного странными. - person cletus; 16.01.2009

Это терпит неудачу из-за стирания. Подробнее об этом можно прочитать в этих учебниках по Java.

person Ryan Ahearn    schedule 16.01.2009

Я думаю, это совершенно нормально. На мой взгляд, использование Thing t = new Thing(); для универсального включенного класса - это полная ошибка. Когда компилятор видит универсальный класс, используемый как класс без параметра типа, он думает, что должен стереть все общие типы из этого класса. Вот как вы можете компилировать старые коды без использования дженериков в новых java-компиляторах, а компилятор позволяет этим старым кодам без проблем использовать универсальные классы с поддержкой (например, java.util.ArrayList). (и именно поэтому java не нуждается в разделенных System.Collection.Generic.List и System.Collection.List, таких как C#). Вы можете запустить Thing t = new Thing(); с добавлением к нему простого параметра типа, Thing<Object> t = new Thing<Object>();, единственное, что нужно java-компилятору, - это убедиться, что вы используете java generic сознательно. Я никогда не могу винить Java за ее прекрасную обратную совместимость.

Я знаю, что немного опоздал :D

person Ebrahim Byagowi    schedule 04.04.2012