Я читал об удалении типов Java на веб-сайте Oracle .
Когда происходит стирание типа? Во время компиляции или во время выполнения? Когда класс загружается? Когда будет создан экземпляр класса?
Многие сайты (включая упомянутое выше официальное руководство) говорят, что стирание типов происходит во время компиляции. Если информация о типе полностью удаляется во время компиляции, как JDK проверяет совместимость типов, когда вызывается метод, использующий обобщения, без информации о типе или неправильной информации о типе?
Рассмотрим следующий пример: Скажем класс A
имеет метод, empty(Box<? extends Number> b)
. Компилируем A.java
и получаем файл класса A.class
.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Теперь мы создадим еще один класс , B
который вызывает метод empty
с непараметризированным аргументом (сырье типа): empty(new Box())
. Если мы составляем B.java
с A.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<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
Я предполагаю, что стирание типов происходит при загрузке класса, но это всего лишь предположение. Так когда же это случится?
источник
Ответы:
Тип стирания относится к использованию дженериков. Там определенно метаданных в файле класса , чтобы сказать , является ли метод / тип является универсальным, и какие ограничения и т.д. Но когда дженерики используются , они преобразуются в чеки во время компиляции и выполнения времени слепков. Итак, этот код:
составлен в
Во время выполнения нет способа узнать, что
T=String
для объекта списка - эта информация исчезла.... но сам
List<T>
интерфейс все еще объявляет себя как универсальный.РЕДАКТИРОВАТЬ: просто чтобы уточнить, компилятор сохраняет информацию о том, что переменная является
List<String>
- но вы все еще не можете узнать этоT=String
для самого объекта списка.источник
List<? extends InputStream>
как вы можете узнать, какой он был при создании? Даже если вы можете узнать тип поля, в котором сохранена ссылка, зачем вам это нужно? Почему вы должны иметь возможность получать всю остальную информацию об объекте во время выполнения, а не его аргументы общего типа? Похоже, вы пытаетесь изобразить стирание типа как крошечную вещь, которая на самом деле не влияет на разработчиков - хотя в некоторых случаях я считаю, что это очень серьезная проблема.Компилятор отвечает за понимание Generics во время компиляции. Компилятор также отвечает за отбрасывание этого «понимания» универсальных классов в процессе, который мы называем стиранием типов . Все происходит во время компиляции.
Примечание. Вопреки убеждениям большинства разработчиков Java, можно хранить информацию о типе во время компиляции и извлекать эту информацию во время выполнения, несмотря на то, что она очень ограничена. Другими словами: Java действительно предоставляет обобщенные обобщенные формы очень ограниченным образом .
По поводу стирания типа
Обратите внимание , что во время компиляции, компилятор имеет полную информацию о типе доступна , но эта информация намеренно упала в общем , когда байт - код генерируется в процессе , известном как тип стирания . Это сделано из-за проблем совместимости: целью разработчиков языка было обеспечение полной совместимости исходного кода и полной байтового кода между версиями платформы. Если бы это было реализовано по-другому, вам пришлось бы перекомпилировать ваши старые приложения при переходе на более новые версии платформы. Таким образом, все сигнатуры методов сохраняются (совместимость с исходным кодом), и вам не нужно ничего перекомпилировать (двоичная совместимость).
Относительно усовершенствованных дженериков в Java
Если вам нужно хранить информацию о типе времени компиляции, вам нужно использовать анонимные классы. Дело в том, что в очень особом случае анонимных классов можно получить полную информацию о типе во время компиляции во время выполнения, что, другими словами, означает: reified generics. Это означает, что компилятор не выбрасывает информацию о типе, когда задействованы анонимные классы; эта информация хранится в сгенерированном двоичном коде, и система времени выполнения позволяет вам получать эту информацию.
Я написал статью на эту тему:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
Заметка о методике, описанной в статье выше, заключается в том, что метод неясен для большинства разработчиков. Несмотря на то, что это работает и работает хорошо, большинство разработчиков чувствуют смущение или неудобство с техникой. Если у вас есть общая кодовая база или вы планируете опубликовать свой код для общественности, я не рекомендую описанную выше технику. С другой стороны, если вы являетесь единственным пользователем своего кода, вы можете воспользоваться преимуществами этой технологии.
Образец кода
В статье выше есть ссылки на пример кода.
источник
new Box<String>() {};
не в случае косвенного доступа,void foo(T) {...new Box<T>() {};...}
поскольку компилятор не сохраняет информацию о типе для объявление метода вложения.Если у вас есть поле универсального типа, параметры его типа скомпилированы в класс.
Если у вас есть метод, который принимает или возвращает универсальный тип, эти параметры типа компилируются в класс.
Эта информация является то , что компилятор использует , чтобы сказать вам , что вы не можете передать
Box<String>
вempty(Box<T extends Number>)
метод.API сложен, но вы можете проверить эту информацию типа через Reflection API с методами , как
getGenericParameterTypes
,getGenericReturnType
и для полей,getGenericType
.Если у вас есть код, который использует универсальный тип, компилятор вставляет приведенные значения при необходимости (в вызывающую программу) для проверки типов. Сами универсальные объекты являются просто необработанным типом; параметризованный тип "стерт". Итак, когда вы создаете
new Box<Integer>()
, нет информации оInteger
классе вBox
объекте.Часто задаваемые вопросы Анжелики Лангер - лучший справочник, который я видел для Java Generics.
источник
Generics in Java Language - действительно хорошее руководство по этой теме.
Итак, это во время компиляции. JVM никогда не узнает, что
ArrayList
вы использовали.Я также рекомендовал бы ответ г-на Скита о том, что такое концепция стирания в дженериках в Java?
источник
Стирание типа происходит во время компиляции. Что означает стирание типа, так это то, что он забудет об общем типе, а не о каждом типе. Кроме того, все еще будут метаданные о типах, являющихся общими. Например
преобразуется в
во время компиляции. Вы можете получать предупреждения не потому, что компилятор знает, какой тип является родовым, а наоборот, потому что он не знает достаточно, поэтому он не может гарантировать безопасность типов.
Кроме того, компилятор сохраняет информацию о типе параметров в вызове метода, которую вы можете получить с помощью отражения.
Это руководство - лучшее, что я нашел по этому вопросу.
источник
Термин «стирание типа» на самом деле не является правильным описанием проблемы Java с обобщениями. Стирание типов само по себе не является плохой вещью, оно действительно необходимо для производительности и часто используется в нескольких языках, таких как C ++, Haskell, D.
Перед отвращением, пожалуйста, вспомните правильное определение стирания типа из Wiki
Что такое стирание типа?
Стирание типа означает отбрасывание тегов типа, созданных во время разработки, или предполагаемых тегов типа во время компиляции, так что скомпилированная программа в двоичном коде не содержит никаких тегов типа. И это относится к каждому языку программирования, компилируемому в двоичный код, за исключением некоторых случаев, когда вам нужны теги времени выполнения. Эти исключения включают в себя, например, все экзистенциальные типы (ссылочные типы Java, которые являются подтипами, любой тип во многих языках, объединенные типы). Причиной удаления типа является то, что программы преобразуются в язык, который в некотором роде не типизирован (двоичный язык допускает только биты), поскольку типы являются только абстракциями и утверждают структуру для своих значений и соответствующую семантику для их обработки.
Так что это взамен, нормальная естественная вещь.
Проблема Java в другом и вызвана тем, как она обновляется.
Часто высказываемые утверждения о Java не имеют обобщенных обобщений, что также неверно.
Java действительно совершенствуется, но неправильно из-за обратной совместимости.
Что такое овеществление?
Из нашей вики
Реификация означает преобразование чего-то абстрактного (Параметрический тип) в нечто конкретное (Конкретный тип) по специализации.
Мы проиллюстрируем это на простом примере:
ArrayList с определением:
это абстракция, в деталях конструктор типа, который становится «ограниченным», когда специализируется на конкретном типе, скажем, Integer:
где
ArrayList<Integer>
на самом деле тип.Но это именно то, чего не делает Java !!! вместо этого они постоянно связывают абстрактные типы со своими границами, то есть производят один и тот же конкретный тип независимо от параметров, передаваемых для специализации:
который здесь ограничен неявным связанным объектом (
ArrayList<T extends Object>
==ArrayList<T>
).Несмотря на это, это делает универсальные массивы непригодными и вызывает некоторые странные ошибки для необработанных типов:
это вызывает много неясностей, как
обратитесь к той же функции:
и поэтому перегрузка универсального метода не может быть использована в Java.
источник
Я столкнулся с типом стирания в Android. В производстве мы используем gradle с опцией minify. После минификации я получил роковое исключение. Я сделал простую функцию, чтобы показать цепочку наследования моего объекта:
И есть два результата этой функции:
Не минимизированный код:
Минимизированный код:
Таким образом, в минимизированном коде фактические параметризованные классы заменяются необработанными типами классов без какой-либо информации о типах. В качестве решения для моего проекта я удалил все вызовы рефлексии и заменил их явными типами params, передаваемыми в аргументах функции.
источник