Ошибка компиляции при наследовании универсального внутреннего класса, расширяющегося с ограничениями

У меня проблема при компиляции универсального класса с внутренним классом. Класс расширяет общий класс, а также внутренний класс.

Здесь реализован интерфейс:

public interface IndexIterator<Element>
    extends Iterator<Element>
{
  ...
}

Общий суперкласс:

public abstract class CompoundCollection<Element, Part extends Collection<Element>>
    implements Collection<Element>
{
  ...

  protected class CompoundIterator<Iter extends Iterator<Element>>
      implements Iterator<Element>
  {
    ...
  }
}

Общий подкласс с ошибкой компилятора:

public class CompoundList<Element>
    extends CompoundCollection<Element, List<Element>>
    implements List<Element>
{
  ...

  private class CompoundIndexIterator
      extends CompoundIterator<IndexIterator<Element>>
      implements IndexIterator<Element>
  {
    ...
  }
}

Ошибка:

type parameter diergo.collect.IndexIterator<Element> is not within its bound
       extends CompoundIterator<IndexIterator<Element>>
                                             ^

Что не так? Код компилируется с помощью eclipse, но не с компилятором java 5 (я использую ant с java 5 на Mac и eclipse 3.5). Нет, я не могу преобразовать его в статический внутренний класс.


person Arne Burmeister    schedule 09.04.2010    source источник
comment
Есть ли шанс, что вы могли бы попробовать это с Java 6? Это может быть ошибка компилятора.   -  person Michael Myers    schedule 10.04.2010
comment
Только что попробовал с Java 6. То же сообщение об ошибке. Интересный...   -  person Eyal Schneider    schedule 10.04.2010
comment
@mmyers: хорошая идея @Eval: спасибо, тоже пробовал на mac 1.6 jdk - та же ошибка   -  person Arne Burmeister    schedule 10.04.2010


Ответы (2)


Спецификация языка Java, §8.1.3, определяет семантику создания подклассов внутренних типов следующим образом:

Кроме того, для каждого надкласса S класса C, который сам является прямым внутренним классом класса SO, существует экземпляр SO, связанный с i, известный как непосредственно вмещающий экземпляр i по отношению к S. Непосредственно вмещающий экземпляр объекта по отношению к прямому суперклассу своего класса, если таковой имеется, определяется, когда конструктор суперкласса вызывается с помощью явного оператора вызова конструктора.

Обратите внимание, что объемлющий экземпляр описывается только как относящийся к определенному классу, а не к определенному типу. Поскольку все экземпляры универсального типа используют один и тот же класс, следующий код будет допустимым:

class Base<E> {
    E e;

    protected class BaseInner<I extends E>{
        E e() { return e; }
    } 
} 

class StrangeSub extends Base<Integer> {
    protected class StrangeSubInner extends Base<String>.BaseInner<String> {}
}

Конечно, это можно использовать для нарушения инварианта типа (т. е. вызвать загрязнение кучи):

    StrangeSub ss = new StrangeSub();
    ss.e = 42;
    String s = ss.new StrangeSubInner().e();

Компилятор eclipse принимает Спецификацию языка Java за чистую монету и принимает приведенный выше код, даже не выдавая «непроверенное» предупреждение. Хотя это, возможно, технически соответствует JLS, это явно нарушает его намерения.

Компилятор Sun Java отклоняет объявление StrangeSubInner следующим образом:

Test.java:32: type parameter java.lang.String is not within its bound
        protected class StrangeSubInner extends Base<String>.BaseInner<String> {}
                                                                       ^

По-видимому, компилятор не просто проверял параметр типа на соответствие параметру типа внутреннего суперкласса, как это делало eclipse. В данном случае я считаю это правильным поступком, так как декларация явно небезопасна. Однако компилятор Sun в равной степени отклоняет следующее объявление, даже если оно доказуемо безопасно для типов:

class StrangeSub extends Base<Integer> {
    protected class StrangeSubInner extends BaseInner<Integer> {}
}

Я подозреваю, что проверка непротиворечивости этих ограничений типа ромба выходит за рамки возможностей компилятора Sun, и поэтому такие конструкции вместо этого безоговорочно отвергаются.

Чтобы обойти это ограничение, я бы сначала попытался избавиться от параметра типа в CompoundIterator.

person meriton    schedule 10.04.2010
comment
Спасибо, поэтому мне нужно сделать это по-старому - просто привести (для члена использовался общий). - person Arne Burmeister; 10.04.2010

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

class Base<E> { 
    protected class BaseInner<I extends E>{
    } 
} 

class Sub<E> extends Base<E>{ 
    class SubInner extends BaseInner<E> { 
    }
} 
person Eyal Schneider    schedule 09.04.2010