Raw-типы и унаследованный код

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

Чтобы облегчить переход к обобщениям, Java позволяет обобщенным классам быть использованными без аргументов типа. Это создает raw-тип ("сырой" тип) для класса. Этот raw-тип совместим с унаследованным кодом, который не имеет представления об обобщенном синтаксисе. Главный недостаток использования raw-типа в том, что безопасность типов утрачивается.

Вот пример, демонстрирующий raw-тип в действии:

// Демонстрация raw-типа.
class Gen {
Т ob; // Объявление объекта типа Т
// Передача конструктору ссылки
//на объект типа Т.
Gen(T о) { ob = о;
}
// Возврат ob.
Т getob () { return ob;
}
}
// Демонстрация raw-типа.
class RawDemo {
public static void main(String args[]) {
// Создать объект Gen для Integer.
Gen iOb = new Gen(88);
// Создать объект Gen для String.
Gen strOb = new Gen("Обобщенный тест");
// Создать объект raw-типа Gen и дать ему значение Double.
Gen raw = new Gen (new Double (98 . 6) ) ;
// Приведение необходимо, поскольку тип неизвестен.
double d = (Double) raw.getobO;
System.out.println("значение: " + d) ;
// Использование raw-типов может вызвать исключения
// времени выполнения. Ниже представлены некоторые примеры.
// Следующее приведение вызовет ошибку времени выполнения!
// int i = (Integer) raw.getob();
// ошибка времени выполнения
// Это присваивание нарушает безопасность типов.
strOb = raw; // OK, но потенциально неверно
// String str = strOb.getob();
// ошибка времени выполнения
// Это присваивание также нарушает безопасность типов,
raw = iOb; // OK, но потенциально неверно
// d = (Double) raw.getob(); // ошибка времени выполнения
}
}

С этой программой связано несколько интересных моментов. Во-первых, следующее объявление создает класс Gen raw-типа:

Gen raw = new Gen (new Double (98. 6) ) ;

Обратите внимание, что никаких аргументов типа не указывается. По сути, оператор создает объект Gen, чей тип Т заменяется Obj ect.

Raw-типы не обеспечивают безопасности, То есть переменный raw-типа можно присвоить ссылку на любой тип объектов Gen. Обратное также возможно: переменной специфического типа Gen можно присвоить ссылку на объект raw-типа Gen. Однако обе операции потенциально небезопасны, так как механизм проверки типов при этом обходится.

Недостаток безопасности иллюстрируется закомментированными строками в конце программы. Рассмотрим каждую из них. Вот первая ситуация:

// int i = (Integer) raw.getobO;
// ошибка времени выполнения

В этом операторе получается значение ob внутри raw, и это значение приводится к типу Integer. Проблема в том, что raw содержит значение Double вместо целого. Однако это не может быть обнаружено на этапе компиляции, поскольку тип raw неизвестен. То есть этот оператор вызовет сбой во время выполнения.

Следующая последовательность присваивает strOb (ссылке на Gen) ссылку на объект Gen: strOb = raw; // OK, но потенциально неверно
// String str = strOb.getob();
// ошибка времени выполнения

Это присваивание само по себе синтаксически корректно, но сомнительно. Поскольку strOb имеет тип Gen, предполагается, что оно содержит String. Однако после присваивания объект, на который ссылается strOb, содержит Double. То есть во время выполнения, когда предпринимается попытка присвоить strOb переменной str, происходит ошибка времени выполнения, так как strOb теперь содержит Double. То есть присваивание raw-ссылки обобщенной ссылке минует механизм проверки типов.

Следующая последовательность представляет собой противоположный случай:

raw = iOb; // OK, но потенциально неверно
// d = (Double) raw.getobO;
// ошибка времени выполнения

Здесь обобщенная ссылка присваивается переменной raw-ссылки. Хотя это синтаксически корректно, но также может привести к проблемам, как показывает вторая строка. В этом случае raw теперь ссылается на объект, содержащий Integer, но приведение предполагает, что в нем содержится Double. Эта ошибка не может быть предотвращена во время компиляции. Вместо этого она проявляется во время выполнения.

Из-за того, что raw-типы представляют опасность, j avac отображает непроверенные предупреждения, когда обнаруживает, что их использование может нарушить безопасность типов. В предыдущей программе следующие строки вызовут появление таких предупреждений:

Gen raw = new Gen (new Double(98.6));
strOb = raw; // OK, но потенциально неверно

В первой строке происходит вызов конструктора Gen без аргумента типа; что вызывает предупреждение. Во второй строке происходит присваивание raw-ссылки обобщенной переменной, что также вызывает появление предупреждения.

На первый взгляд, может показаться, что эта строка также должна порождать предупреждение, но этого не происходит:

raw = iOb; // OK, но потенциально неверно

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

Один заключительный момент: вы должны ограничивать использование raw-типов теми случаями, когда приходится смешивать унаследованный код с новым, обобщенным. Raw-типы — это просто средство переноса, а не что-то такое, что должно применяться в новом коде.