При добавлении обобщений к языку Java команда Java хотела удовлетворить следующие требования (среди прочих):
- Код, скомпилированный для Java 4, и код, скомпилированный для Java 5, должны быть в состоянии сосуществоватьв одной и той же JVM и беспрепятственно взаимодействуют
- Код библиотеки, написанный для Java 4, должен иметь возможность переноситься на Java 5 таким образом, чтобы не нарушать бинарную совместимость
Вместе этитребования обеспечивают возможность постепенного и независимого переноса существующего кода Java в новую языковую версию, обеспечивая его широкое распространение с минимальным нарушением работы экосистемы Java.
Основная трудность возникла из-за модернизации инфраструктуры сбора Java API сгенерики таким образом, чтобы не нарушать существующий код.Например, рассмотрим библиотеку Java 4, которая объявляет:
public List getFactories();
и используется в существующем приложении Java 4, которое:
List list = getFactories();
Теперь предположим, что приложение обновлено до Java 5до библиотеки.Какой тип параметра они указывают?Они могут сказать:
List factories = getFactories();
... но это лишает новый код возможности использовать дженерики
List<Object> factories = getFactories()
... но это не избавляет от ненужных приведений в коде приложения - что хорошего в генериках, если они остаются?Кроме того, как только библиотека определит параметр типа, код приложения должен будет обновляться во второй раз, чтобы соответствовать ему.
List<Factory> factories = getFactories();
..... но это не будет безопасным типом: так как во время создания не был указан параметр типа при создании объекта List
, он не может проверить, что List
содержит только фабрики (или принудительно установить, что это так и есть).устаревший код продолжает взаимодействовать с List
)
List<Factory> factories = new ArrayList<Factory>(getFactories);
..., который будет работать, но копирование всего списка может быть медленным
Команда Java выбрала вариант 3 и частично смягчила воздействие, потребовав, чтобы компилятор предупреждал об отсутствии безопасности типов, и попросив компилятор вставлять синтетические приведения для проверки типа каждого отдельного объекта всписок перед использованием, тем самым сохраняя целостность системы типов времени выполнения при небольшом снижении производительности (и значительных затратах здравомыслия разработчика при сбое такого приведения).
Интересно отметить, что команда разработчиков C #, столкнувшаяся по существу с тем же затруднением при введении обобщений в C # и CLR несколькими годами ранее, решила нарушить обратную совместимость, чтобы преобразовать универсальные типы.Как следствие, им пришлось ввести совершенно новую платформу для сбора (а не просто модифицировать существующую платформу для сбора, как это сделала Java).
Оглядываясь назад, я думаю, что команда .NET здесь сделала лучший выбор, потому что это позволило им развивать язык, а не увековечивать его ограничения.По крайней мере, обоснование их проектных решений не нуждается в объяснении спустя десятилетие: -)