Подтвердить что ты не робот

Ошибка компилятора в Java-интерфейсе с помощью метода List <>

Я не понимаю ошибку компилятора, вытекающую из следующего кода. Я определяю общий интерфейс, см. "Задача", с двумя способами: U doSomething(String value) и List<Integer> getIDs(). Метод doSomething() фактически использует тип generic как тип возвращаемого значения, но, похоже, не вызывает проблем. Метод getIDs() возвращает список, который не связан с типом задачи, но он вызывает проблемы при использовании for.each для итерации по возвращаемому значению. Произошла следующая ошибка компилятора.

error: incompatible types
    for (Integer value : task.getIDs()){
required: Integer
found:    Object

Кажется, что стирание типа на интерфейсе заставляет компилятор забыть объявленный тип во втором методе, который не имеет отношения к родовому типу. Или, другими словами, почему общий тип интерфейса влияет на то, как компилятор понимает возвращаемое значение в методе getIDs() и, в частности, в контексте выражения for..each?

По-видимому, если я получаю ссылку на список за пределами for..each, нет проблем, но не напрямую.

public class InterfaceTest {
   public static void main(String[] args) {
      Task task = new MyTask();
      // no complaints about the type here     
      List<Integer> values = task.getIDs();

      // getting a compiler error for this line
      for (Integer value : task.getIDs()){

      }
   }
}


interface Task<U>{
   U doSomething(String value);
   List<Integer> getIDs();
}

Реализация интерфейса не требуется для демонстрации точки, но я не хочу оставлять ссылку Task task = null; и иметь ответ, говорящий мне, что проблема.

class MyTask implements Task<Boolean>{

   @Override
   public Boolean doSomething(String value) {
      System.out.println(value);
      return false;
   }

   @Override
   public List<Integer> getIDs() {
      return Arrays.asList( 1, 2, 3, 4 );
   }
}
4b9b3361

Ответ 1

Что происходит, когда вы используете класс (или интерфейс) с общим параметром <T>, но обратитесь к экземпляру без <T> (т.е. типа raw), компилятор стирает всю информацию об общем типе из класса. Вероятно, это связано с совместимостью с исходным кодом до 1.5, где вы вообще не сможете использовать информацию общего типа.

Рассмотрим ситуацию, когда вы пишете код и компилируете компилятор Java 1.4. Вы хотите использовать библиотеку, которая использует дженерики. Когда вы ссылаетесь на тип из той библиотеки, которая имеет общие параметры как необработанный тип, компилятор принудительно использует не общие параметры.

EDIT:

JLS-4.8-210 ссылается на это, когда упоминает (credit: zhong-j-yu):

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

Это по-прежнему похоже на получение, но по какой-то причине это возможно.

Ответ 2

Кажется, что ошибка здесь:

Task task = new MyTask();

Вы забыли добавить дженерики после Task. Он должен работать, если вы измените его на один из следующих:

Task<Boolean> task = new MyTask();
Task<?> task = new MyTask();

Ответ 3

Если я правильно интерпретирую Специфика языка Java (§4.6. Тип Erasure), это "gotcha" языка:

Стирание стилей также отображает подпись (§8.4.2) конструктора или метода на подпись, которая не имеет параметризованных типов или переменных типа. Стирание знака конструктора или метода s является сигнатурой, состоящей из того же имени, что и s, и стираниями всех формальных типов параметров, заданных в s.

Я считаю, что это означает, что если вы объявляете тип (Task), объявленный с помощью общего параметра (Task<U>) без указанного общего параметра, все его функции также теряют свои общие типы, независимо от того, связаны они или не. Поэтому ваш task.getIDs() интерпретируется компилятором как возвращающий простой List, а не List<Integer>. Итератор для этого, конечно, создает Objects, а не Integers, вызывая ошибку компилятора.

Причиной этого является, вероятно, обратная совместимость с кодом, созданным до Java 1.5, когда были введены генерики.