2017-02-15 3 views
11

Я создал MWE, где смена одной строки путем добавления <?> решает ошибку компилятора.Опуская <?> unintuitly нарушает этот код

Следующий код не компилируется:

import java.util.List; 

public class MainClass { 

    public void traverse() { 
     List<MyEntity> list = null /* ... */; 
     for (MyEntity myEntity : list) { 
      for (String label : myEntity.getLabels()) { // <-- Offending Line 
       /* ... */ 
      } 
     } 
    } 

    interface MyEntity<T> { 
     T get(); 

     List<String> getLabels(); 
    } 
} 

Ошибка компилятора:

Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String 

Изменение определения в ошибочную строку из MyEntity myEntity в MyEntity<?> myEntity решает эту проблему. Интересно, почему тип возврата для for-each рассматривается как Object, а не как String, если я не добавлю шаблон к родительскому классу? Обратите внимание, что getLabels() сам по себе не содержит дженериков.

Согласно Chapter 14.14.2. of the Java Language Specification, каждый из них скомпилирован в цикл с использованием итератора. Интересно отметить, что расширение для-каждого такому итератора вручную работы:

Iterator<String> iterator = myEntity.getLabels().iterator(); 
while (iterator.hasNext()) { 
    String label = iterator.next(); 
    /* ... */ 
} 

Может кто-нибудь объяснить, почему?

+0

Ницца один, никогда не видел, что раньше – Andremoniy

ответ

9

Прежде всего, тело метода вашего примера кода может быть упрощена:

public void traverse() { 
    MyEntity myEntity = null; 
    for (String label : myEntity.getLabels()) { // <-- Offending Line 
      /* ... */ 
    } 
} 

Почему это происходит? Потому что, когда вы объявляете переменную myEntity (не важно где - в для цикла или, как в моем примере), как MyEntity myEntity, вы объявляете его как сырого типа, исключающих общий тип от типа возвращаемого значения методы getLabels, а также : поэтому он становится таким же, как List getLabels();, где, очевидно, для построения петли ожидается Object.

В то же время Iterator<String> iterator = myEntity.getLabels().iterator(); работает отлично, потому что здесь указывается тип явно: Iterator<String>.


Очень похожий пример приведен в JLS 4.8 "Raw types", который объясняет, почему это происходит:

... наследственные члены типа, которые зависят от переменных типа будет наследуется как сырья типов, как следствие правило, что стили стираются ...

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

class Outer<T>{ 
    class Inner<S> { 
     S s; 
    } 
} 

Это не представляется возможным получить доступ к Inner, как частично сырой тип ("редкий" типа):

Outer.Inner<Double> x = null; // illegal 

UPD-2: Как я получил вопросы о Iterator<String> iterator = myEntity.getLabels().iterator();, почему это нормально, в то время как первый пример не работает?

Я лично согласен с тем, что это выглядит запутанным. Но таковы правила. Этот случай также рассматриваются в том же JLS пункте с этим примером:

class Cell<E> { 
    E value; 

    Cell(E v)  { value = v; } 
    E get()  { return value; } 
    void set(E v) { value = v; } 

    public static void main(String[] args) { 
     Cell x = new Cell<String>("abc"); 
     System.out.println(x.value); // OK, has type Object 
     System.out.println(x.get()); // OK, has type Object 
     x.set("def");     // unchecked warning 
    } 
} 

Более тщательное объяснение о том, почему делает Iterator<String> iterator = myEntity.getLabels().iterator(); работы с JLS основаны на этом правиле:

То есть, подтипирование правила (§4.10.2) программирования Java язык позволяет присвоить переменную типа raw значение любого из параметризованных экземпляров типа

В том же, как вы всегда можете написать хорошо скомпилированный код, как:

List<String> labels = myEntity.getLabels(); 
    for (String label : labels) { // <-- OK, no error here 
      /* ... */ 
    } 
+0

Спасибо! Мне нужно еще некоторое разъяснение: Предполагая, что, как вы сказали, объявление myEntity как raw также делает все члены сырыми, как я могу затем сохранить результат 'myEntity.getLabels()' в (не сырой!) ' Итератор '? Разве это не должно быть незаконным? –

+1

Это немного другая ситуация. 'Iterator Итератор()' содержит переменную типа, а не общий тип. Когда вы укажете этот тип в 'Iterator iterator', вы фактически« восстановите »этот тип, поэтому он работает даже в том случае, если' list' он сам является сырым типом. ** Но **, потому что это не прямой способ указания типа, он даст вам сообщение об отключении *. – Andremoniy

+0

Непонятно, что 'List labels = myEntity.getLabels()' A-OK для компилятора. Это связано с тем, что в задании разрешено какое-то ограничение типа? –

Смежные вопросы