Почему неопределенные универсальные типы коллекций по умолчанию имеют значение Object, даже если они привязаны к другому классу?

class Test<G extends String>{
    public G test(){return null;}
    public List<G> tests(){return new ArrayList<>();}
}
public void doTest(Test t){
    //works fine
    String str = t.test();
    //Compile error: expected String found Object
    str = t.tests().iterator().next();
}

Я хотел бы, чтобы последняя строка возвращала экземпляр String вместо Object, поскольку тип G был привязан к подклассу String. Есть ли другой способ, кроме кастинга?


person Gus    schedule 11.06.2014    source источник
comment
Потому что с необработанными типами определенная вами общая граница никогда не рассматривается в первую очередь, как если бы вы вообще никогда не обобщали свой класс. Частично это связано с тем, что до Java 1.5 дженериков не существовало в Java, и не было бы способа реализовать описанное вами поведение.   -  person awksp    schedule 11.06.2014
comment
В этом случае t.test() не должен также возвращать объект?   -  person Gus    schedule 11.06.2014
comment
Хм... Интересный момент. Пропустил это сначала. Я полагаю, в этом случае это было бы потому, что возвращаемый ArrayList тогда был бы необработанным типом. Не совсем уверен, почему t.test() вернет String.   -  person awksp    schedule 11.06.2014
comment
G extends String Есть ли смысл в этом типе дженерика. String - неизменяемый конечный класс. В этом случае просто используйте Test<String>. Попробуйте new ArrayList<G>()   -  person Braj    schedule 11.06.2014
comment
Я использовал String только для демонстрации, то же самое происходит и с другими классами. Я обновлю вопрос.   -  person Gus    schedule 12.06.2014


Ответы (1)


Нет, типа как стерли. Вы сталкиваетесь с двумя разными правилами стирания типов: из JLS раздел 4.6:

  • Стирание параметризованного типа (§4.5) G есть |G|.
  • Стирание переменной типа (§4.4) — это стирание ее крайней левой границы.

Таким образом, стирание List<G> равно List, а стирание G равно String, поэтому первое присваивание работает.

Все, что вам нужно сделать, чтобы этот код скомпилировался, это использовать подстановочный знак в вашем параметре:

public void doTest(Test<?> t)
person Jon Skeet    schedule 11.06.2014
comment
Хммм, просто любопытно - что означает возвращаемый тип ? для next()? Приведет ли это к исключению во время выполнения, если типы не работают? Или это безопасно, потому что G всегда String? - person awksp; 11.06.2014
comment
@ user3580294: Это безопасно, потому что G всегда совместим по присваиванию с String... в основном у вас есть Iterator<? extends String> для вызова next(). - person Jon Skeet; 12.06.2014
comment
Как ни странно, Eclipse сообщает мне, что iterator() возвращает Iterator<?>, что меня немного смущает, но ваш ответ имеет смысл. - person awksp; 12.06.2014
comment
@ user3580294: Возможно, вы захотите попробовать Test<? extends String> t и посмотреть, изменит ли это Eclipse. Кажется, в javac все в порядке - хотя это с JDK 8, и все могло измениться с JDK 7... - person Jon Skeet; 12.06.2014
comment
О, извините, что не выразился более ясно; компилирует в обе стороны, просто изначально всплывающая подсказка при наведении на iterator() показывала, что вернуло Iterator<?>. Изменение объявления параметра действительно изменяет указанный тип возвращаемого значения. - person awksp; 12.06.2014