Очистка

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

Важное ограничение, которое было наложено на способ реализации обобщений в Java, заключалось в том, что необходимо было обеспечить совместимость с предыдущими версиями Java. Только представьте, что обобщенный код должен быть совместимым со старым, не обобщенным, кодом. То есть любые изменения в синтаксисе языка Java либо в JVM должны были избегать разрушения старого кода. Способ, которым Java реализует обобщения для удовлетворения этому требованию — это так называемая очистка (erasure).

В общих чертах вот как она работает. При компиляции вашего Java-кода вся информация об обобщенных типах удаляется (чистится). Это означает замену параметров типа их ограничивающим типом, который является Object, если только никакого явного ограничения не указано, с последующим использованием необходимых приведений (как того требуют аргументы типа) для поддержки совместимости типов с типами, указанными в аргументах. Компилятор также обеспечивает эту совместимость типов. Такой подход к обобщениям означает, что никакой информации о типах во время выполнения не существует. Это просто механизм автоматической обработки исходного кода.

Чтобы лучше понять, как работает очистка, рассмотрим два следующих класса:

// Здесь Т ограничен типом Object по умолчанию.
class Gen {
,Т ob; // Здесь Т будет заменен Object
Gen(T о) {
ob = о;
}
// Возвращает ob.
Т getob () { return ob;
}
}
// Здесь Т ограничен String.
class GenStr {
T str; // Здесь T будет заменен String
GenStr(T o) {
str = o;
}
T getstrf) {
return str; }
}

После того, как эти классы компилируются", Т в Gen будет заменен Object. Т в GenStr будет заменен String. Вы можете убедиться в этом, запустив javap на скомпилированных классах. Результат будет выглядеть так:

class Gen extends java.lang.Object {
java.lang.Object ob;
Gen(java.lang.Object);
java.lang.Object getob();
}
class GenStr extends java.lang.Object)
java.lang.String str;
GenStr(java.lang.String);
java.lang.String getstrO;
}

Внутри кода Gen и GenStr для обеспечения корректной типизации применяются приведение. Например, следующая последовательность:

Gen iOb = new Gen(99);
int x = iOb.getob() ;

будет скомпилирована, как если бы она была написана так:

Gen iOb = new Gen(99);
int x = (Integer) iOb.getobO;

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

class GenTypeDemo {
public static void main(String args[]) {
Gen iOb = new Gen(99);
Gen fob = new Gen(102.2F);
System.out.println(iOb.getClass().getName());
System.out.println(fOb.getClass().getName());

Вывод этой программы показан ниже:

Gen
Gen

Как видите, типом и iOb, и fOb является Gen, а не Gen и Gen, как вы могли ожидать. Помните, что все параметры типов во время компиляции удаляются. Во время выполнения существуют только raw-типы.