Связывание методов и бездумный копипаст



Вопрос выше связан с темой связывания методов. Начну с неё, а потом немного поворчу на интернет.



Итак, связывание бывает:

▫️ Позднее (динамическое) — решение, какой метод вызвать, принимается во время работы программы

▫️ Раннее (статическое) — решение принимается на этапе компиляции. В рантайме нет лишних движений, и скорость вызова таких методов чуть выше



Для статических методов работает (сюрприз) статическое связывание. Статические методы в классах Parent и Child не переопределяют друг друга и относятся к разным классам. Над методом в классе Child нельзя поставить Override и вызвать внутри super.getName();



Поэтому правильный ответ в опросе выше — "Parent". Метод определяется во время компиляции по типу указателя.



Что из этого можно вынести?



Если посмотреть на getName, то непонятно, статический он или обычный, учитывается тип экземпляра или нет. Это затрудняет чтение кода и считается плохой практикой. Настолько плохой, что Intellij IDEA даже не показывает статические методы в выпадающем списке для объекта.



Поэтому best practice — вызывать статические методы, обращаясь к классу:

Parent.getName()



Теперь о грустном.



Вопрос про связывание часто входит в списки java interview questions, но почти все статьи содержат неверную информацию. Пишут, что



▪️ Статическое связывание используют private, final, static методы и конструкторы

▪️ Динамическое — методы интерфейсов и перегруженные методы



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



javap -c -v LovelyService.class



или в IDEA: View → Show Bytecode



Нас интересуют инструкции invoke*. Немного поиграв с кодом можно увидеть, что для public, protected, private и final методов используется invokevirtual — динамическая типизация. Статические методы используют инструкцию invokestatic.



Сразу возникает вопрос:



Почему для private и final методов используется динамическое связывание? Ведь метод точно не переопределяется и это известно во время компиляции



Для final ответ кроется в спецификации java, пункт 13.4.17. Суть такая: final метод может однажды перестать быть final, и кто-то может его переопределить. Когда класс, который переопределил метод, будет взаимодействовать со старым байткодом, ничего не должно сломаться.



Правила работы с private методами описаны в спецификации JVM, пункт 5.4.6. Причина использования invokevirtual не указана, но подозреваю, что ситуация как у final



Зачем это знать?



Для написания кода это абсолютно не важно. Но это яркий пример некорректной информации, которая бездумно копируется в джуниорские опросники👎



А вот на курсе многопоточки мы постоянно лазаем по исходникам java.util.concurrent, поэтому инфа абсолютно живая и актуальная. Минутка рекламы, но почему бы и нет:) Присоединяйтесь: https://fillthegaps.ru/mt6