Компилятор должен вставлять инструкции проверки типов на уровне байтового кода, где это необходимо, поэтому при присвоении Object
, например. Object o = x.get(0);
или System.out.println(x.get(0));
, может не требовать этого, вызов метода для выражения x.get(0)
требует этого.
Причина кроется в правилах совместимости бинарных файлов . Проще говоря, не имеет значения, был ли вызванный метод унаследован или явно объявлен типом получателя, формальный тип выражения x.get(0)
равен Integer
, и вы вызываете для него метод getClass()
, следовательно, вызов будет закодирован как вызов метода с именем getClass
с сигнатурой () → java.lang.Class
в классе получателя java.lang.Integer
. Тот факт, что этот метод был унаследован от java.lang.Object
и что он был объявлен final
во время компиляции, не отражается в скомпилированном классе.
Таким образом, теоретически во время выполнения метод можно было бы удалить из java.lang.Object
и добавить новый метод java.lang.Class getClass()
в java.lang.Integer
без нарушения совместимости с этим конкретным кодом. Хотя мы знаем, что этого никогда не произойдет, компилятор просто следует формальным правилам, чтобы не вводить в код предположения о наследовании.
Поскольку вызов будет скомпилирован как вызов, нацеленный на java.lang.Integer
, перед инструкцией вызова необходимо выполнить приведение типа, что приведет к сбою в сценарии загрязнения кучи.
Обратите внимание, что если вы измените код на
System.out.println(((Object)x.get(0)).getClass());
вы сделаете явное предположение, что метод был объявлен в java.lang.Object
. Расширение до java.lang.Object
не будет генерировать никаких дополнительных инструкций байт-кода, все, что делает этот код, — это изменяет тип получателя вызова метода на java.lang.Object
, устраняя необходимость в приведении типа.
Здесь есть интересное отклонение от правил: компилятор делает кодирование вызова как вызов java.lang.Object
на уровне байт-кода, если метод является одним из известных final
методов, объявленных в java.lang.Object
. Это может быть связано с тем, что эти конкретные методы указаны в JLS, и их кодирование в этой форме позволяет JVM быстро идентифицировать эти специальные методы. Но комбинация инструкции checkcast
и инструкции invokevirtual
по-прежнему демонстрирует такое же совместимое поведение.
person
Holger
schedule
21.06.2017