Стирание стилей стилей Java: когда и что происходит?

Я прочитал о стирании типа Java на веб-сайте Oracle .

Когда происходит стирание типа? Во время компиляции или времени выполнения? Когда class загружен? Когда экземпляр classа создается?

Многие сайты (включая официальный урок, упомянутый выше) говорят, что стирание стилей происходит во время компиляции. Если информация о типе полностью удалена во время компиляции, как совместимость типа проверки JDK при вызове метода с использованием дженериков без информации типа или неверной информации о типе?

Рассмотрим следующий пример: Say class A имеет метод, empty(Box b) . Мы компилируем A.java и получаем файл classа A.class .

 public class A { public static void empty(Box b) {} } 
 public class Box {} 

Теперь мы создаем другой class B который вызывает метод empty с непараметрированным аргументом (raw type): empty(new Box()) . Если мы скомпилируем B.java с A.class в пути к classам, javac достаточно умен, чтобы поднять предупреждение. Поэтому у A.class есть некоторая информация о типе, хранящаяся в нем.

 public class B { public static void invoke() { // java: unchecked method invocation: // method empty in class A is applied to given types // required: Box // found: Box // java: unchecked conversion // required: Box // found: Box A.empty(new Box()); } } 

Мое предположение заключалось бы в том, что стирание типа происходит, когда class загружается, но это всего лишь предположение. Итак, когда это происходит?

Тип стирания применяется к использованию дженериков. В файле classа есть определенные метаданные, чтобы сказать, является ли метод / тип обобщенным, и какие ограничения существуют и т. Д. Но когда используются дженерики, они преобразуются в проверки времени компиляции и касты выполнения. Итак, этот код:

 List list = new ArrayList(); list.add("Hi"); String x = list.get(0); 

скомпилирован в

 List list = new ArrayList(); list.add("Hi"); String x = (String) list.get(0); 

Во время выполнения нет способа узнать, что T=String для объекта списка – эта информация исчезла.

… но сам интерфейс List прежнему рекламирует себя как универсальный.

EDIT: просто чтобы уточнить, компилятор сохраняет информацию о переменной, являющейся List но вы все еще не можете узнать, что T=String для самого объекта списка.

Компилятор отвечает за понимание Generics во время компиляции. Компилятор также отвечает за исключение этого «понимания» общих classов в процессе, который мы называем стиранием типа . Все происходит во время компиляции.

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

Что касается стирания стилей

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

Что касается обобщенных дженериков в Java

Если вам нужно сохранить информацию типа времени компиляции, вам необходимо использовать анонимные classы. Дело в том, что в особом случае анонимных classов можно получить полную информацию типа времени компиляции во время выполнения, что, другими словами, означает: reified generics. Это означает, что компилятор не выбрасывает информацию о типе, когда участвуют анонимные classы; эта информация хранится в сгенерированном двоичном коде, а система времени выполнения позволяет получить эту информацию.

Я написал статью по этому вопросу:

http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html

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

Образец кода

В приведенной выше статье есть ссылки на пример кода.

Если у вас есть тип общего типа, его параметры типа компилируются в class.

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

Эта информация используется компилятором, чтобы сообщить вам, что вы не можете передать Box в empty(Box) метод empty(Box) .

API сложный, но вы можете проверить информацию этого типа через API отражения с помощью методов getGenericParameterTypes , getGenericReturnType и для полей getGenericType .

Если у вас есть код, который использует общий тип, компилятор вставляет при необходимости (в вызывающем абоненте) для проверки типов. Общие объекты сами по себе являются только сырым типом; параметризованный тип «стирается». Итак, когда вы создаете new Box() , информация о classе Integer в объекте Box .

Часто задаваемые вопросы по Angelika Langer – лучшая ссылка, которую я видел для Java Generics.

Дженерики на языке Java – действительно хорошее руководство по этой теме.

Дженерики реализуются компилятором Java как интерфейсное преобразование, называемое стиранием. Вы можете (почти) думать об этом как о переводе источника в источник, в результате чего общая версия loophole() преобразуется в не-общую версию.

Итак, это во время компиляции. JVM никогда не узнает, какой ArrayList вы использовали.

Я также рекомендовал бы г-н Скит ответить на вопрос: Какова концепция стирания в дженериках на Java?

Стирание типа происходит во время компиляции. Какой тип стирания означает, что он забудет о родовом типе, а не о каждом типе. Кроме того, все еще будут метаданные о типичных типах. Например

 Box b = new Box(); String x = b.getDefault(); 

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

 Box b = new Box(); String x = (String) b.getDefault(); 

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

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

Это руководство – лучшее, что я нашел по этому вопросу.

Я столкнулся с типом стирания в Android. В производстве мы используем gradle с параметром minify. После майнинга у меня есть фатальное исключение. Я сделал простую функцию для отображения цепочки наследования моего объекта:

 public static void printSuperclasses(Class clazz) { Type superClass = clazz.getGenericSuperclass(); Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName())); Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString())); while (superClass != null && clazz != null) { clazz = clazz.getSuperclass(); superClass = clazz.getGenericSuperclass(); Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName())); Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString())); } } 

И есть два результата этой функции:

Недопустимый код:

 D/Reflection: this class: com.example.App.UsersList D/Reflection: superClass: com.example.App.SortedListWrapper D/Reflection: this class: com.example.App.SortedListWrapper D/Reflection: superClass: android.support.v7.util.SortedList$Callback D/Reflection: this class: android.support.v7.util.SortedList$Callback D/Reflection: superClass: class java.lang.Object D/Reflection: this class: java.lang.Object D/Reflection: superClass: null 

Мини-код:

 D/Reflection: this class: com.example.App.UsersList D/Reflection: superClass: class com.example.App.SortedListWrapper D/Reflection: this class: com.example.App.SortedListWrapper D/Reflection: superClass: class android.support.v7.ge D/Reflection: this class: android.support.v7.ge D/Reflection: superClass: class java.lang.Object D/Reflection: this class: java.lang.Object D/Reflection: superClass: null 

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

Термин «стирание типа» на самом деле не является правильным описанием проблемы Java с генериками. Стирание стилей само по себе плохое, действительно, оно очень необходимо для производительности и часто используется на нескольких языках, таких как C ++, Haskell, D.

Прежде чем вы отвратитесь, пожалуйста, вспомните правильное определение стирания типа из Wiki

Что такое стирание типа?

type erasure относится к процессу загрузки-времени, с помощью которого annotations явного типа удаляются из программы, прежде чем они будут выполнены во время выполнения

Тип erasure означает выбросить tags типа, созданные во время разработки или tags inferred type во время компиляции, так что скомпилированная программа в двоичном коде не содержит тегов типа. И это имеет место для каждого языка программирования, компилирующего двоичный код, за исключением случаев, когда вам нужны tags времени выполнения. Эти исключения include, например, все типы экзистенции (типы ссылок Java, которые являются подтипированными, любой тип на многих языках, типы Union). Причина стирания стилей заключается в том, что программы преобразуются в язык, который в некотором роде унифицирован (бинарный язык допускает только биты), поскольку типы являются только абстракциями и утверждают структуру его значений и соответствующую семантику для их обработки.

Так что это в свою очередь, нормальная естественная вещь.

Проблема Java различна и вызвана тем, как она восстанавливается.

Часто сделанные заявления о Java не содержат повторно генерируемых дженериков.

Java действительно подтверждает, но неправильно, из-за обратной совместимости.

Что такое овеществление?

Из нашей вики

Обоснование – это процесс, посредством которого абстрактная идея об компьютерной программе превращается в явную модель данных или другой объект, созданный на языке программирования.

Reification означает преобразование чего-то абстрактного (Parametric Type) в нечто конкретное (конкретный тип) по специализации.

Проиллюстрируем это на простом примере:

ArrayList с определением:

 ArrayList { T[] elems; ...//methods } 

представляет собой абстракцию, подробно конструктор типа, который получает «reified», когда специализируется на конкретном типе, например Integer:

 ArrayList { Integer[] elems; } 

где ArrayList – действительно тип.

Но это именно то, чего нет на Java ! , вместо этого они постоянно обновляют абстрактные типы с их границами, т. е. производят один и тот же конкретный тип, не зависящий от параметров, передаваемых для специализации:

 ArrayList { Object[] elems; } 

который здесь инициализируется неявным связанным объектом ( ArrayList == ArrayList ).

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

 List l= List.of("h","s"); List lRaw=l l.add(new Object()) String s=l.get(2) //Cast Exception 

это вызывает много неясностей, как

 void function(ArrayList list){} void function(ArrayList list){} void function(ArrayList list){} 

обратитесь к той же функции:

 void function(ArrayList list) 

и поэтому универсальная перегрузка метода не может использоваться в Java.

Interesting Posts
Давайте будем гением компьютера.