Обобщения повышают безопасность типов

Теперь вы можете задать себе следующий вопрос: если та же функциональность, которую мы обнаружили в обобщенном классе Gen, может быть достигнута без обобщений, т.е. простым указанием Object в качестве типа данных и применением правильных приведений, в чем же выгода от того, что класс Gen параметризован? Ответ: в том, что обобщения автоматически гарантируют безопасность типов во всех операциях, где задействован Gen. В процессе работы с ним исключается необходимость явного приведения и ручной проверки типов в коде.

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

// NonGen — функциональный эквивалент Gen
//не использующий обобщений.
class NonGen {
Object ob; // ob теперь имеет тип Object
// Передать конструктору ссылку на объект типа Object
NonGen(Object о) {
ob = о;
}
// Вернуть тип Object.
Obj ect getob() {
return ob;
}
// Показать тип ob. void showType() {
System.out.println("Типом ob является " + ob.getClass().getName() ) ;
}
}
// Демонстрация необобщенного класса.
class NonGenDemo {
public static void main(String args[]) {
NonGen iOb;
// Создать объект NonGen и сохранить
// Integer в нем. Автоупаковка используется.
iOb = new NonGen (88);
// Показать тип данных, используемый iOb.
iOb.showType();
// Получить значение iOb.
//На этот раз приведение необходимо.
int v = (Integer) iOb.getob ();
System.out.println("значение: " + v) ;
System.out.println ();
// Создать другой объект NonGen и
// сохранить в нем String.
NonGen strOb = new NonGen("Тест без обобщений");
// Показать тип данных, используемый strOb.
strOb.showType () ;
// Получить значение strOb.
// Опять же — приведение необходимо.
String str = (String) strOb.getob () ;
System.out.println("Значение: " + str);
// Это компилируется, но концептуально неверно!
iOb = strOb;
v = (Integer) iOb.getob (); // ошибка времени выполнения!
}
}

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

Обратите внимание на строку:

int v = (Integer) iOb.getob();

Поскольку типом возврата getob() является Object, необходимо привести его к Integer, чтобы позволить выполнить автораспаковку и сохранить значение в v. Если убрать приведение, программа не скомпилируется. В версии с обобщением приведение происходит неявно. В версии без обобщения приведение должно быть явным. Это не только неудобство, но также потенциальный источник ошибок.

Теперь рассмотрим следующую кодовую последовательность в конце программы: // Это компилируется, но концептуально неверно!
iOb = strOb;
v = (Integer) iOb.getobO;
// ошибка времени выполнения!

Здесь strOb присваивается iOb. Однако strOb ссылается на объект, содержащий строку, а не целое число. Это присваивание синтаксически корректно, потому что все ссылки NonGen одинаковы, и любая ссылка NonGen может указывать на любой другой объект типа NonGen. Однако этот оператор семантически неверен, что и отражено в следующей строке. Здесь тип возврата getob () приводится к Integer, и затем делается попытка присвоить это значение v. Проблема в том, что iOb теперь ссылается на объект, который хранит String, а не Integer. К несчастью, без использования обобщений компилятор Java не имеет возможности обнаружить это. Вместо этого возбуждается исключение времени выполнения. Возможность создавать безопасный в отношении типов код, в котором ошибки несоответствия типов перехватываются компилятором — это главная выгода от обобщений. Хотя использование ссылок на Object для создания "псевдо-обобщенного" кода всегда возможно, нужно помнить, что такой код не является безопасным в отношении типов, и злоупотребление им приводит к исключениям времени выполнения.

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