Почему компилятор Java жалуется на использование foreach с необработанным типом?

У меня возникла странная ошибка компилятора при использовании дженериков в цикле for-each в Java. Это ошибка компилятора Java или мне что-то здесь действительно не хватает?

Вот весь мой класс:

public class Generics<T extends Object> {
  public Generics(T myObject){
    // I didn't really need myObject
  }

  public List<String> getList(){
    List<String> list = new ArrayList<String>();
    list.add("w00t StackOverflow");
    return list;
  }

  public static void main(String...a){
    Generics generics = new Generics(new Object());
    for(String s : generics.getList()){
      System.out.println(s);
    }
  }
}

Компилятор жалуется на строку с for-each: «Несоответствие типов не может преобразовать из типа элемента Object в String».
Если я сделаю это тонкое изменение, он скомпилирует:

public static void main(String...a){
  Generics<?> generics = new Generics(new Object());
  for(String s : generics.getList()){
    System.out.println(s);
  }
}

Я знаю, что getList() действительно использует дженерики, но он использует их, как я думал, совершенно не связанным образом. Я мог бы понять это, если бы пытался перебрать что-то типа T, а getList() вернул List<T> или что-то в этом роде, но здесь это не так. Тип возвращаемого значения getList() не должен иметь ничего общего с T и не должен заботиться о том, использую ли я необработанный тип для своего объекта Generics или нет ... верно? Разве они не должны быть совершенно не связаны друг с другом, или я действительно чего-то здесь упускаю?

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

public static void main(String...a){
  Generics generics = new Generics(new Object());
  List<String> list = generics.getList();
  for(String s : list){
    System.out.println(s);
  }
}

person Michael McGowan    schedule 25.03.2011    source источник
comment
<T extends Object> ничем не отличается от <T>. Вы не создаете общую версию своего класса, вы создаете необработанный тип. Это подводит нас к вопросу, почему ваш класс вообще является универсальным? Единственное место, где вы используете T, - это конструктор, и вы не используете эту ссылку.   -  person unholysampler    schedule 25.03.2011
comment
Я использовал <T extends Object>, потому что мне просто нужно было что-то для примера. Настоящий код, очевидно, является чем-то другим, и он действительно использует T ... он просто использует T способом, совершенно не связанным с getList().   -  person Michael McGowan    schedule 25.03.2011
comment
не имеет отношения к вашему вопросу, но я бы сделал конструктор Generics ‹Class ‹T› cls), чтобы вам не нужно было создавать экземпляр объекта типа T только для создания этого класса Generics.   -  person MeBigFatGuy    schedule 25.03.2011
comment
Я действительно просто хотел продемонстрировать <T extends *something*>, не акцентируя внимание на том, что это было за что-то, поэтому я выбрал Object для своего примера.   -  person Michael McGowan    schedule 25.03.2011


Ответы (3)


Разница в том, что при использовании необработанного типа все общие ссылки в сигнатурах членов также преобразуются в их необработанные формы. Так эффективно вы вызываете метод, который теперь имеет такую ​​подпись:

List getList()

Теперь что касается того, почему ваша окончательная версия компилируется - хотя это так, есть предупреждение, если вы используете -Xlint:

Generics.java:16: warning: [unchecked] unchecked conversion
    List<String> list = generics.getList();
                                        ^

Это похоже на:

 List list = new ArrayList();
 List<String> strings = list;

... который также компилируется, но с предупреждением под -Xlint.

Мораль этой истории: не используйте сырые типы!

person Jon Skeet    schedule 25.03.2011
comment
Я очень удивлен, что все общие ссылки в подписях членов преобразованы в их необработанные формы. Какова причина этого (кроме того, что Солнце просто так захотелось)? - person Michael McGowan; 25.03.2011
comment
@Michael: JLS включает это обсуждение в разделе 4.8 (сырые типы): сырые типы тесно связаны с подстановочными знаками. Оба основаны на экзистенциальных типах. Необработанные типы можно рассматривать как подстановочные знаки, правила типов которых намеренно неверны, чтобы обеспечить взаимодействие с унаследованным кодом. Другими словами, необработанные типы обычно не должны появляться в новом коде, но они пытались избежать сбоя компиляции старого кода, даже если это было хотя бы подозрительно. - person Jon Skeet; 25.03.2011
comment
Очень интересно. Я уже знал, что следует избегать использования необработанных типов (коллега написал код для объявления переменной), но это подчеркивает, что это действительно может иметь значение. - person Michael McGowan; 25.03.2011
comment
@ michael-mcgowan Я думаю, в какой-то момент они действительно устали от огромных деталей дженериков, и именно здесь они думают, что могут срезать углы и сэкономить часть своего времени. Если бы у них было больше времени / энергии, они бы не оставили это в таком небрежном состоянии. - person irreputable; 26.03.2011

Измените строку

Generics generics = new Generics(new Object());

to

Generics<?> generics = new Generics<Object>(new Object());

Корень вашей проблемы в том, что вы используете необработанный тип, поэтому тип метода getList - List, а не List<String>.

person Mike Samuel    schedule 25.03.2011
comment
Generics НЕ будет универсальным для типа String ... в этом весь смысл. Строка не связана с типом Generics. Независимо от T getList() должен вернуть List<String>. - person Michael McGowan; 25.03.2011
comment
@ Майкл Макгоуэн, хорошее замечание. Но должен быть какой-то тип, связанный с параметром типа в точке объявления. Generics<?> generics = new Generics(...); было бы хорошо, по модулю предупреждения о небезопасном преобразовании. - person Mike Samuel; 25.03.2011
comment
@Michael McGowan, обратите внимание, что если бы вы только удалили параметр типа <T extends Object> из объявления класса, тогда это сработало бы. - person Mike Samuel; 25.03.2011

Я внес пару изменений в ваш код. Вы видите в своем комментарии, что вам не нужен Object в вашем конструкторе, поэтому давайте удалим его, чтобы избежать путаницы. Во-вторых, если Generics будет универсальным, инициализируйте его должным образом.

Вот как будет выглядеть новый главный

public static void main(String...a){
    Generics<String> generics = new Generics<String>();
    for(String s : generics.getList()){
      System.out.println(s);
    }
  }
person Sean    schedule 25.03.2011
comment
Generics НЕ будет универсальным для типа String ... в этом весь смысл. Строка не связана с типом Generics. - person Michael McGowan; 25.03.2011
comment
Я думаю, вы неправильно поняли мою точку зрения. Если вы посмотрите на код, у вас есть метод getList (), возвращающий List ‹String›. Если бы мы хотели сделать код по-настоящему чистым, мы могли бы убрать часть обобщенного кода из кода, кроме метода getList (). вы просили помощи, чтобы собрать это, а не то, был ли его подход правильным / неправильным. - person Sean; 25.03.2011
comment
Если вы оставите общее замедление на уровне класса, вы откроете метод getList (), чтобы удалить жесткое кодирование, которое существует сейчас. - person Sean; 25.03.2011