Обобщения в Java – часть 2 - Реализация обобщений

ОГЛАВЛЕНИЕ

Реализация обобщений

Обобщения – средство уровня языка Java. Одной из целей разработки обобщений было сохранение двоичной совместимости на уровне байтового кода. Не требуя никаких изменений в JVM и сохранив такой же формат файлов класса (байтовый код), можно с легкостью смешать обобщенный код и необобщенный код. Но это дорого обходится. Можно ослабить безопасность типов, прежде всего обеспечиваемую обобщениями.

Имеет ли значение то, что обобщения находятся на уровне языка, а не на уровне байт-кода? Есть две причины для беспокойства. Первая – если это только средство уровня языка: что произошло бы, если бы и когда бы другие языки работали на виртуальной машине Java(JVM)? Если другие языки, работающие на JVM, являются динамическими языками (Groovy, Ruby, Python, …), то ничего страшного. Но если попытаться запустить строго типизированный язык на JVM –  возникнет проблема. Вторая причина – если это просто средство уровня языка (по сути, макрос), то можно было бы передавать правильные типы во время выполнения, например, с помощью Reflection.

Увы, обобщения в Java не обеспечивают эффективную безопасность типов. Они не служат полностью для того, для чего были созданы.

Стирание

Если обобщения являются средством уровня языка, что происходит при компиляции обобщенного кода? Код лишается всех параметрических типов, и каждая ссылка на параметрический тип заменяется классом (как правило, Object или нечто более специальное). Этот процесс называется стиранием типов.
По документации: “Главное преимущество данного подхода в том, что он обеспечивает полную совместимость между обобщенным кодом и устаревшим кодом, использующим непараметризованные типы (именуемые сырыми типами). Основные недостатки в том, что информация о типе параметра недоступна при выполнении, и что автоматически сгенерированные приведения типов могут не сработать при взаимодействии с некорректно функционирующим устаревшим кодом. Однако есть способ добиться гарантированной безопасности типов времени выполнения для обобщенных коллекций даже при взаимодействии с некорректно функционирующим устаревшим кодом.

Несмотря на то, что это обеспечивает взаимодействие обобщенного и необобщенного кода, это компрометирует безопасность типов. Рассматривается действие стирания на код.

Рассмотрите пример кода:

class MyList<T>
{
    public T
ref;
}

Запуск javap –c дает следующий байтовый код:

javap -c MyList
Compiled from "Test.java"
class com.agiledeveloper.MyList extends java.lang.Object{
public java.lang.Object ref;

com.agiledeveloper.MyList();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

Тип T члена ref класса был стерт на (заменен на) тип Object.

Не все типы всегда стираются на или заменяются на Object. Взгляните на следующий пример:

class MyList<T extends Vehicle>
{
    public T ref;
}

В данном случае тип T заменяется на Vehicle, как показано ниже:

javap -c MyList
Compiled from "Test.java"
class com.agiledeveloper.MyList extends java.lang.Object{
public com.agiledeveloper.Vehicle ref;

com.agiledeveloper.MyList();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

Теперь рассмотрите пример:

class MyList<T extends Comparable>
{
    public T ref;
}

Здесь тип T заменяется на интерфейс Comparable.

Наконец, если используется ограничение нескольких пределов, как в:

class MyList<T extends Vehicle & Comparable>
{
    public T ref;
}

то тип T заменяется на Vehicle. Первый тип в ограничении нескольких пределов используется как тип при стирании.

Воздействие стирания

Рассматривается воздействие стирания на код, использующий обобщенный тип. Посмотрите пример:

ArrayList<Integer> lst  = new ArrayList<Integer>();
lst.add(new Integer(1));
Integer val = lst.get(0);

Это преобразуется в:

ArrayList lst = new ArrayList();
lst.add(new Integer(1));
Integer val = (Integer) lst.get(0);

При присваивании lst.get(0) к val производится приведение типа в преобразованном коде. Если бы код был написан без применения обобщений, делалось бы то же самое. Обобщения в Java, в этом смысле, служат синтаксическим сахаром.

Местонахождение?

Был разобран способ обращения с обобщениями в Java и  была рассмотрена степень обеспечения безопасности типов. Еще ряд проблем, связанных с обобщениями, будет обсуждаться в следующей части.

Заключение

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