Общие типизированные внутренние классы в Java

Я учился и экспериментировал с Java Generics некоторое время, но я столкнулся с чем-то, что я не могу объяснить. Возьмем, к примеру, следующий код:

public class Question {
    public <T> Sub<T> getSub(Class<T> c) {
        return new Sub<T>(c);
    }
    public class Sub<S> {
        private Class<S> c;
        public Sub(Class<S> c) {
            this.c = c;
        }
        public void add(S s) {
        }
    }
}

И тестовый код:

import generics.Question.Sub;

public class Answer {
    public static void main(String [] args) {
        Question q = new Question();
        Sub<String> s = q.getSub(String.class);
        s.add("");
    }
}

Когда это выполняется, он дает удивительно загадочное сообщение об ошибке:

C:\Answer.java:8: incompatible types
found   : generics.Question.Sub<java.lang.String>
required: generics.Question.Sub<java.lang.String>
        Sub<String> s = q.getSub(String.class);
1 error

Теперь, после некоторого эксперимента, я разработал, как предотвратить ошибку компилятора. Я могу либо сделать Sub-класс статическим внутренним классом, либо мне нужно обратиться к Sub-классу как Question.Sub <String> . Я не могу объяснить, почему мне нужно это делать.

Я прочитал документацию Java по Generics, но ни один из них не касается этого конкретного случая.

Может ли кто-нибудь объяснить, почему код является несовместимым типом в его текущей форме?

-Edit -

Глядя на это ближе, я вижу, что я получаю такое же поведение за пределами Netbeans. Если у меня есть код в следующей структуре:

generics\
generics\Question.java
generics\Answer.java

Когда я компилирую файлы вместе, я не получаю ошибку:

C:\>javac generics\Question.java generics\Answer.java

C:\>

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

C:\>javac generics\Question.java

C:\>javac generics\Answer.java
generics\Answer.java:8: incompatible types
found   : generics.Question.Sub<java.lang.String>
required: generics.Question.Sub<java.lang.String>
        Sub<String> s = q.getSub(String.class);
                                ^
1 error

Я слышал что-то, упомянутое о Type Erasure. Так ли это в этой ситуации?

Ответы

Ответ 1

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

Map<String,String> map = new HashMap<String,String>();

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

Map other = (Map)map;
other.put(new Integer(3), new Double( 4.5 );

Проблема заключается в том, что скомпилированный код не проверяет типы аргументов при передаче, а также не выполняет runtime (поскольку информация о типе была стерта, следовательно, введите стирание).

Я сомневаюсь, что стирание типа является проблемой здесь, поскольку во время компиляции у вас есть полная информация о типе, но скорее это, вероятно, ошибка. Там довольно много волосатых проблем с дженериками (из реализации), и есть разные компиляторы, которые используются с JavaC и Eclipse, поэтому могут появляться разные ошибки. В некоторых случаях компилятор Eclipse был более точным для спецификации, чем у компилятора Sun (поэтому Eclipse создает ошибки, в то время как Sun не делает этого), и это в основном из-за сложности того, как работает система типов.

Итак, это, скорее всего, одна (или более) ошибка с генериками в компиляторе 1.5.0_14...