Дженерики и Тип-стирание


10

Обобщения в Java реализованы с использованием стирания типов. JLS говорит, что вдохновением была обратная совместимость. Где, как и с другой стороны, дженерики C # являются reifiable.

Теоретически, что является преимуществами и недостатками, если Generics будет «стирать» или «переопределять»?

На что-то не хватает Java?

Ответы:


8

Что ж, мотивация (обратная совместимость) является как преимуществом, так и недостатком. Это невыгодно, потому что мы все предпочли бы иметь типы reifiable, но цена была высока. Рассмотрим варианты дизайна в C #. У них есть типы reifiable, но теперь у них есть дубликаты API. Итак, представьте Java API, где у нас также были дублированные API для каждого параметризованного класса. Теперь представьте, что вы переносите тысячи строк кода из устаревших классов в новые универсальные классы. Теперь, кто бы не считал дубликаты API недостатками? Но эй, у них есть reifiable типы!

Итак, основной мотивацией была «эволюция, а не революция». И логически, каждое решение имеет компромиссы.

Помимо других упомянутых недостатков, мы могли бы также добавить тот факт, что стирание типов может быть трудно рассуждать во время компиляции, так как не очевидно, что определенные типы будут удалены, и это приводит к очень странным и трудным для поиска ошибкам.

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

Основные недостатки вытекают из уже очевидного факта, что для универсальных типов существует один класс, а не несколько классов. В качестве другого примера рассмотрим, что перегрузка метода с таким же универсальным классом завершается неудачно в Java:

public void doSomething(List<One>);
public void doSomething(List<Two>);

Кое-что, что может быть замечено как недостаток reifiable типов (по крайней мере в C #), является фактом, что они вызывают взрыв кода . Например List<int>, один класс, а List<double>другой - совершенно другой, так как это List<string>и a List<MyType>. Таким образом, классы должны быть определены во время выполнения, вызывая взрыв классов и потребляя ценные ресурсы, пока они генерируются.

Что касается того факта, что невозможно определить new T()в Java, упомянутого в другом ответе, также интересно учитывать, что это не только вопрос стирания типа. Это также требует существования конструктора по умолчанию, поэтому C # требует для этого «нового ограничения». (См. Почему новый T () невозможен в Java , автор Алекс Бакли).


3
Я думаю, что я прав, говоря, что в CLR для ссылочного типа T s вы получаете только одну копию Class<T>кода для всех Ts; плюс дополнительная копия для каждого фактически используемого типа значения T .
AakashM

@AakashM: Правильно, существует один класс ссылочного типа для каждого универсального, однако каждый тип значения создает свою собственную версию. Это связано с предоставлением специализированного пространства хранения на основе типа, все ссылки занимают одинаковое хранилище, но типы значений занимают разные хранилища для каждого типа.
Guvante

6

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

Вы не можете сделать что-то подобное в Java:

public class MyClass<T> {
    private T t;

    //more methods    
    public void myMethod() {
        //more code
        if (someCondition) {
            t = new T();//illegal
        } else {
            T[] array = new T[];//illegal
        }            
    }       
}

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


3

Другое преимущество стертых дженериков заключается в том, что разные языки, которые компилируются в JVM, используют разные стратегии для дженериков, например, сайт определения Scala или ковариация сайта использования Java. Кроме того, обобщенные родословные Scala было бы труднее поддерживать на типах .Net, так как по сути Scala в .Net игнорировал несовместимый формат C # для преобразования. Если бы мы внедрили дженерики в JVM, скорее всего, эти дженерики не подойдут для функций, которые нам действительно нравятся в Scala, и мы застряли бы в чем-то неоптимальном. Цитата из блога Олы Бини ,

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

Лично я не рассматриваю необходимость использования TypeTagконтекста, связанного в Scala для конфликтующих перегруженных методов, как недостаток, потому что он переносит накладные расходы и негибкость реификации с глобального (целая программа и все возможные языки) на сайт использования для каждого языка проблема, которая только в случае реже.



Другая причина отдать предпочтение стертым родовым типам заключается в том, что зависимости должны вводиться таким образом, чтобы это соответствовало решению проблемы выражения. Если вы тестируете на конкретные случаи типов или жестко кодируете фабрику в своей универсальной функции, то вы неправильно расширяете возможности.
Шелби Мур III
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.