Ответ 1
ACC_SUPER был введен для исправления проблемы с вызовом супер-методов. Флаг ACC_SUPER отмечает класс, скомпилированный для измененной семантики команды opcode 183. Его цель аналогична цели номера версии файла класса, поскольку она позволяет JVM определять, был ли класс скомпилирован для более старой или более новой семантики этой команды. Java 1.0.2 не устанавливал и не игнорировал ACC_SUPER, в то время как Java 1.1 и более поздние версии всегда устанавливали ACC_SUPER.
Перед Java 1.1 команда байтового кода с кодом операции 183, который теперь называется invokespecial
, был вызван invokenonvirtual
и имел частично другую спецификацию. Он использовался всякий раз, когда методы экземпляра должны были вызываться без поиска виртуального метода. Это было сделано для частных методов, инициализаторов экземпляров (конструкторов) и для реализации вызовов метода на super
. Но последний случай вызвал проблемы с развивающимися библиотеками классов.
Ссылка на метод в байтовом коде (CONSTANT_Methodref_info
) не только определяет имя и аргумент и возвращает типы метода, но также класс, к которому он принадлежит. Код операции 183 получает такой ссылочный параметр метода и предназначен для непосредственного вызова ссылочного метода из указанного класса без дополнительных поисков. В случае вызовов на super
ответственность компиляторов заключалась в разрешении ближайшего суперкласса, который реализует этот метод и генерирует ссылку на него в байтовый код.
Так как Java 1.1 был изменен, чтобы по существу игнорировать класс, на который ссылается CONSTANT_Methodref_info
, и вместо этого выполняет поиск ближайшего супер метода с данным именем метода и сигнатурой в JVM. Обычно это делается, когда класс загружается или находится прямо перед выполнением команды или скомпилирован JIT в первый раз.
Вот пример, почему это изменение было необходимо. В Java 1.0.2 классы AWT Container и Component были определены следующим образом:
class Component
{
public void paint( Graphics g ) {}
}
class Container extends Component
{
// inherits paint from Component but doesn't override it
}
В Java 1.1 класс Conatiner был изменен, чтобы иметь собственную реализацию paint
:
class Container extends Component
{
public void paint( Graphics g ) {/*...*/}
}
Теперь, когда у вас был прямой или косвенный подкласс Container, который сделал вызов super.paint(g)
и скомпилировал его для 1.0.2, он сгенерировал инструкцию invokenonvirtual
для Component.paint
, поскольку это был первый родитель, у которого был этот метод, Но если вы использовали этот скомпилированный класс в JVM, который также имел Container.paint
, он все равно назвал бы Component.paint
, что не так, как вы ожидали.
С другой стороны, когда вы скомпилировали класс для 1.1 и выполнили его на JVM версии 1.0.2, он бы выбросил AbstractMethodError или, скорее всего, для виртуальных машин этой эпохи просто сбой. Чтобы избежать сбоя, вам пришлось написать ((Component)super).paint(g)
и скомпилировать его с помощью компилятора 1.1, чтобы получить желаемое поведение в любой виртуальной машине. Это установит ACC_SUPER, но все равно будет генерировать команду для вызова Component.paint
. 1.0.2 VM игнорирует ACC_SUPER и сразу же запускает Component.paint
, что отлично, в то время как 1.1 VM найдет набор ACC_SUPER и, таким образом, сделает сам поиск, который заставит его вызывать Container.paint
, хотя ссылка на метод байтового кода была Component.paint
.
Об этом можно узнать в этот старый пост в блоге ikvm.net.