Неуниверсальная ссылка на универсальный класс приводит к неуниверсальным возвращаемым типам
У меня есть унаследованный класс, который сам по себе не является универсальным, но один из его методов, возвращающий тип, использует обобщенные:
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 универсальным для другого метода, который не указан в приведенном выше примере кода. У меня вопрос воспитательный, почему вышеупомянутое не может быть сделано.
3 ответа
Хорошо, возьми два, я неправильно понял твой вопрос.
Когда вы delcare Thing
(это называется необработанным типом) вместо Thing<?>
(параметризованный тип) компилятор Java удаляет все универсальные аргументы, даже если (как в вашем случае) универсальный тип метода не имеет ничего общего с универсальным типом класса.
Из (превосходного) часто задаваемых вопросов по Java:
Могу ли я использовать необработанный тип, как и любой другой тип?
Методы или конструкторы необработанного типа имеют сигнатуру, которую они имели бы после стирания типа.
Это, казалось бы, безобидное и ненавязчивое предложение описывает поведение, о котором идет речь. Вы используете Thing
как необработанный тип, так что тип возвращаемого значения Collection
(не Collection<String>
) так как это тип после стирания типа.
Смущенный? Неудивительно. Просто посмотрите на размер этого FAQ. Вероятно, в мире около трех человек, которые понимают всю важность Java Generics. Просто рассмотрите мою любимую декларацию от JDK:
Enum<T extends Enum<T>>
(Theres объяснение этого в FAQ тоже).
Это терпит неудачу из-за стирания. Вы можете прочитать больше об этом в этих уроках Java
Я думаю, что это совершенно нормально. По моему мнению, используя 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