Какой принцип нарушен в коде?
Anonymous Quiz
19%
SRP (Принцип единственной ответственности)
46%
DRY (Don't Repeat Yourself)
14%
KISS (Keep It Simple, Stupid)
12%
YAGNI(You Aren't Gonna Need It)
9%
LSP (Принцип подстановки Барбары Лисков)
🤨25👍1😱1
Из чего состоит .class-файл?
.class-файл представляет собой один скомпилированный класс. Это то, что в конечном итоге исполняет Java Virtual Machine. В .class компилируется код любого JVM языка, формат входит в спецификацию и не зависит от платформы или реализации виртуальной машины. Содержимое конкретного класса просматривают утилитой javap из стандартного набора JDK. Подробно структура файла описана на википедии, в документации и во множестве статей. Файл состоит из 10 секций, которые можно условно разбить на группы:
🔘 Свойства файла: определяющее тип файла «волшебное слово» 0xCAFEBABE и версия формата;
🔘 Пул констант – содержит все используемые имена методов и классов в специальном формате, и прочую символьную информацию. В других местах используются только ссылки на элементы пула;
🔘 Основные свойства класса: флаги доступа, имя этого класса, его предка, интерфейсов;
🔘 Внутреннее содержимое: список полей класса и байткод методов
🔘 Атрибуты класса;
Для внутренних классов тоже создаются отдельные файлы с названиями формата OuterClass$InnerClass.class. Если класс анонимный, вместо имени используются номера с 1.
Java Guru🤓 #java
.class-файл представляет собой один скомпилированный класс. Это то, что в конечном итоге исполняет Java Virtual Machine. В .class компилируется код любого JVM языка, формат входит в спецификацию и не зависит от платформы или реализации виртуальной машины. Содержимое конкретного класса просматривают утилитой javap из стандартного набора JDK. Подробно структура файла описана на википедии, в документации и во множестве статей. Файл состоит из 10 секций, которые можно условно разбить на группы:
Для внутренних классов тоже создаются отдельные файлы с названиями формата OuterClass$InnerClass.class. Если класс анонимный, вместо имени используются номера с 1.
Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🔥4
Сколько памяти занимает объект?
Размер экземпляров ссылочных типов, как и примитивов, зависит от конкретной реализации JVM и параметров ее запуска. Обычно в вопросе подразумевается самая популярная машина – HotSpot от Oracle.
Размеры полей-примитивов бывают больше чем необходимо, например в целях выравнивания (alignment). Из-за того же выравнивания между полями в памяти могут возникать пустоты.
Как говорилось ранее, ссылка в Java – не то же самое что указатель в C++, это не адрес в памяти. Из-за этого размер поля-ссылки может не совпадать с размером машинного слова, например когда HotSpot использует оптимизацию «сжатие ссылок» (Compressed OOP).
Кроме полей и промежутков, каждый объект в HotSpot начинается с заголовка – runtime-метаинформации. Заголовок занимает от 8 до 16 байт.
В общем можно только сказать, что размер объекта строго больше суммы размеров его полей. Примерный размер конкретного объекта измеряется средствами инструментации.
Java Guru🤓 #java
Размер экземпляров ссылочных типов, как и примитивов, зависит от конкретной реализации JVM и параметров ее запуска. Обычно в вопросе подразумевается самая популярная машина – HotSpot от Oracle.
Размеры полей-примитивов бывают больше чем необходимо, например в целях выравнивания (alignment). Из-за того же выравнивания между полями в памяти могут возникать пустоты.
Как говорилось ранее, ссылка в Java – не то же самое что указатель в C++, это не адрес в памяти. Из-за этого размер поля-ссылки может не совпадать с размером машинного слова, например когда HotSpot использует оптимизацию «сжатие ссылок» (Compressed OOP).
Кроме полей и промежутков, каждый объект в HotSpot начинается с заголовка – runtime-метаинформации. Заголовок занимает от 8 до 16 байт.
В общем можно только сказать, что размер объекта строго больше суммы размеров его полей. Примерный размер конкретного объекта измеряется средствами инструментации.
Java Guru🤓 #java
👍14🔥6❤5
На какие области делится память JVM?
Следует помнить, что это внутренние особенности HotSpot (и её opensource-версии OpenJDK). В других виртуальных машинах (например в Android) всё может быть абсолютно по-другому. Области-поколения кучи вообще зависят от используемого алгоритма сборки мусора, и могут отличаться в рамках одной и той же реализации виртуальной машины.
🟢 Stack – место под примитивы и ссылки на объекты (но не сами объекты). Хранит локальные переменные и возвращаемые значения функций. Здесь же хранятся ссылки на объекты пока те конструируются. Все данные в стеке – GC roots. Освобождается сразу на выходе из функции. Принадлежит потоку, размер по-умолчанию указывается параметром виртуальной машины -Xss, но при создании потока программно можно указать отличное значение.
🟢 PermGen – В этой области хранятся загруженные классы (экземпляры класса Class<T>). Здесь же с Java 7 хранится пул строк. Изначально размера -XX:PermSize, растет динамически до -XX:MaxPermSize. Не считается частью кучи.
🟢 Metaspace – с Java 8 заменяет permanent generation. Отличие в том, что по умолчанию metaspace ограничен только размерами доступной на машине памяти, но так же как PermGen может быть ограничен, параметром -XX:MaxMetaspaceSize.
🟢 Heap – куча, вся managed-память, в которой хранятся все пользовательские объекты. Все следующие разделы – части кучи. Параметры -Xms, -Xmn и -Xmx устанавливают начальный, минимальный и максимальный размеры хипа соответственно.
🟢 Eden, New Generation, Old Generation и другие – специфичные для сборщика мусора части кучи, поколения. Могут быть разные, но общий подход сохраняется: долго живущий объект постепенно двигается во всё более старое поколение; сборка мусора в разных поколениях происходит раздельно; чем поколение старше, тем сборка в нём реже, но и дороже.
Хотя устройство памяти – это детали реализации виртуальной машины, для Java-разработчика знания о них несут практическую пользу. Эти знания необходимы для передачи правильных значений параметров JVM, что в свою очередь спасает от просадок производительности GC и остановок с OutOfMemoryError.
Java Guru🤓 #java
Следует помнить, что это внутренние особенности HotSpot (и её opensource-версии OpenJDK). В других виртуальных машинах (например в Android) всё может быть абсолютно по-другому. Области-поколения кучи вообще зависят от используемого алгоритма сборки мусора, и могут отличаться в рамках одной и той же реализации виртуальной машины.
Хотя устройство памяти – это детали реализации виртуальной машины, для Java-разработчика знания о них несут практическую пользу. Эти знания необходимы для передачи правильных значений параметров JVM, что в свою очередь спасает от просадок производительности GC и остановок с OutOfMemoryError.
Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥13❤6
Опишите особенности работы с Shutdown Hooks
Shutdown Hook – это обработчик завершения работы JVM. Действия, которые необходимо выполнить при прекращении работы программы, описываются в реализации объекта типа Thread, и передаются в Runtime.getRuntime().addShutdownHook().
При использовании обработчика нужно учитывать ряд нюансов:
1. Нет гарантии, что он будет выполнен целиком, и выполнен вообще. Хуки вызываются при нормальном завершении программы – завершении всех пользовательских потоков или вызове System.exit(). Например вызов System.halt() или получение от системы SIGKILL программа завершится незамедлительно. Это может случиться как до выполнения обработчиков, так и во время. Сигнал SIGTERM запустит обработчики, но ОС может не дождаться завершения и оборвать процесс.
2. В хуке нельзя использовать System.exit(0). Завершить работу можно ненулевым кодом, или методом System.halt(). Возврат нулевого кода после старта хуков приведет к зависанию программы.
3. Набор обработчиков задается до их старта. Когда процесс обработки начался, новый вызов addShutdownHook() вызовет исключение.
4. Нет гарантий о порядке выполнения. Если обработчиков несколько, они могут вызываться в случайном порядке, и даже параллельно, в соседних потоках. Об этом намекает и форма объявления обработчика – класс Thread.
Java Guru🤓 #java
Shutdown Hook – это обработчик завершения работы JVM. Действия, которые необходимо выполнить при прекращении работы программы, описываются в реализации объекта типа Thread, и передаются в Runtime.getRuntime().addShutdownHook().
При использовании обработчика нужно учитывать ряд нюансов:
1. Нет гарантии, что он будет выполнен целиком, и выполнен вообще. Хуки вызываются при нормальном завершении программы – завершении всех пользовательских потоков или вызове System.exit(). Например вызов System.halt() или получение от системы SIGKILL программа завершится незамедлительно. Это может случиться как до выполнения обработчиков, так и во время. Сигнал SIGTERM запустит обработчики, но ОС может не дождаться завершения и оборвать процесс.
2. В хуке нельзя использовать System.exit(0). Завершить работу можно ненулевым кодом, или методом System.halt(). Возврат нулевого кода после старта хуков приведет к зависанию программы.
3. Набор обработчиков задается до их старта. Когда процесс обработки начался, новый вызов addShutdownHook() вызовет исключение.
4. Нет гарантий о порядке выполнения. Если обработчиков несколько, они могут вызываться в случайном порядке, и даже параллельно, в соседних потоках. Об этом намекает и форма объявления обработчика – класс Thread.
Java Guru🤓 #java
👍9🔥3
Можно ли поймать Error?
Технически, Error как и любой другой Throwable можно поймать в блок catch. Такой код абсолютно валидный и скомпилируется без проблем.
На практике, согласно спецификации, значение исключения типа Error – необрабатываемая ошибка, ловить которую нет смысла.
Более того, выброс исключения подтипа VirtualMachineError означает, что JVM находится в сломанном состоянии. Дальнейшая работа непредсказуема: OutOfMemoryError приводит к невозможности создания новых объектов, StackOverflowError теряет фреймы стека вызова, и так далее.
Так как ловить Error не нужно, объявление её в секции throws сигнатуры метода необязательно. Как и RuntimeException, Error – разновидность unchecked exception.
Java Guru🤓 #java
Технически, Error как и любой другой Throwable можно поймать в блок catch. Такой код абсолютно валидный и скомпилируется без проблем.
На практике, согласно спецификации, значение исключения типа Error – необрабатываемая ошибка, ловить которую нет смысла.
Более того, выброс исключения подтипа VirtualMachineError означает, что JVM находится в сломанном состоянии. Дальнейшая работа непредсказуема: OutOfMemoryError приводит к невозможности создания новых объектов, StackOverflowError теряет фреймы стека вызова, и так далее.
Так как ловить Error не нужно, объявление её в секции throws сигнатуры метода необязательно. Как и RuntimeException, Error – разновидность unchecked exception.
Java Guru🤓 #java
👍13🔥5❤2
Какая разница между JDK и JRE?
JRE – Java Runtime Environment, Java-окружение времени выполнения. Это набор всего необходимого чтобы запустить скомпилированную Java-программу. Окружение состоит из виртуальной машины Java (JVM), библиотеки классов (Java Class Library), консольной команды java и прочей инфраструктуры. Тем не менее, JRE не достаточно чтобы создавать новые программы.
JDK – Java Development Kit, набор Java-разработчика. Это полнофункциональный SDK для Java. JDK включает в себя JRE, но кроме того содержит компилятор (javac) и другие инструменты разработки, такие как javadoc и jdb. С помощью JDK можно создавать и компилировать программы.
В большинстве случаев, если вам нужно только запускать Java программы, вы устанавливаете только JRE, если же вы планируете программировать на Java, вместо этого вам необходим JDK.
Java Guru🤓 #java
JRE – Java Runtime Environment, Java-окружение времени выполнения. Это набор всего необходимого чтобы запустить скомпилированную Java-программу. Окружение состоит из виртуальной машины Java (JVM), библиотеки классов (Java Class Library), консольной команды java и прочей инфраструктуры. Тем не менее, JRE не достаточно чтобы создавать новые программы.
JDK – Java Development Kit, набор Java-разработчика. Это полнофункциональный SDK для Java. JDK включает в себя JRE, но кроме того содержит компилятор (javac) и другие инструменты разработки, такие как javadoc и jdb. С помощью JDK можно создавать и компилировать программы.
В большинстве случаев, если вам нужно только запускать Java программы, вы устанавливаете только JRE, если же вы планируете программировать на Java, вместо этого вам необходим JDK.
Java Guru🤓 #java
👍22🔥6❤2
Зачем нужен package-info?
Обычно, чтобы создать новый пакет, вы создаете папку с соответствующим названием, и во всех ее .java файлах добавляете в начале строчку с названием пакета, вида package full.dir.path;. Этого вполне достаточно для работы.
Однако, дополнительно вы можете добавить в пакет его объявление – файл package-info.java. Несмотря на то, что это .java-файл, это не объявление класса. В названии класса дефисы запрещены, так что неоднозначности не возникнет.
Минимальное содержимое файла package-info.java – всё та же строка package full.dir.path;. Такой файл не несет практической пользы. Есть две вещи, которые можно в него добавить, чтобы польза появилась.
Первое – документация пакета. Javadoc-комментарий будет использоваться генератором документации. Пример – описание пакета java.lang. Его исходник вы можете найти в файле java/lang/package-info.java.
Второе возможное применение – аннотации уровня пакета. Для этого аннотация должна иметь Target PACKAGE. Например, некоторые пакеты Spring Framework помечены аннотацией @NonNullApi.
Java Guru🤓 #java
Обычно, чтобы создать новый пакет, вы создаете папку с соответствующим названием, и во всех ее .java файлах добавляете в начале строчку с названием пакета, вида package full.dir.path;. Этого вполне достаточно для работы.
Однако, дополнительно вы можете добавить в пакет его объявление – файл package-info.java. Несмотря на то, что это .java-файл, это не объявление класса. В названии класса дефисы запрещены, так что неоднозначности не возникнет.
Минимальное содержимое файла package-info.java – всё та же строка package full.dir.path;. Такой файл не несет практической пользы. Есть две вещи, которые можно в него добавить, чтобы польза появилась.
Первое – документация пакета. Javadoc-комментарий будет использоваться генератором документации. Пример – описание пакета java.lang. Его исходник вы можете найти в файле java/lang/package-info.java.
Второе возможное применение – аннотации уровня пакета. Для этого аннотация должна иметь Target PACKAGE. Например, некоторые пакеты Spring Framework помечены аннотацией @NonNullApi.
Java Guru🤓 #java
🔥10👍4❤2
Можно ли достать параметры метода рефлекшеном?
По умолчанию – нет. Рефлекшн-информация о параметрах метода представляется объектами класса Parameter. По умолчанию их свойство name синтетическое: "arg0", "arg1", и так далее. Метод isNamePresent при этом возвращает false, что значит что действительная информация об имени недоступна. Она не попадает даже в байт-код.
Начиная с Java 8 у компилятора (javac) появилась опция -parameters. В скомпилированный с этой опцией класс добавляются имена аргументов методов. Parameter#getName() тогда возвращает уже настоящее имя.
Классический пример, когда информация об именах может понадобиться – библиотека JSON сериализации Jackson. Параметры конструктора могут трактоваться ей как поля сериализационной формы.
До появления флага -parameters Jackson использовал для этого аннотации. Каждый параметр снабжался аннотацией @JsonProperty. Значением аннотации передавалась строка, которая повторяла имя параметра. Эти аннотации затем можно было получить, вызвав Method#getParameterAnnotations().
Java Guru🤓 #java
По умолчанию – нет. Рефлекшн-информация о параметрах метода представляется объектами класса Parameter. По умолчанию их свойство name синтетическое: "arg0", "arg1", и так далее. Метод isNamePresent при этом возвращает false, что значит что действительная информация об имени недоступна. Она не попадает даже в байт-код.
Начиная с Java 8 у компилятора (javac) появилась опция -parameters. В скомпилированный с этой опцией класс добавляются имена аргументов методов. Parameter#getName() тогда возвращает уже настоящее имя.
Классический пример, когда информация об именах может понадобиться – библиотека JSON сериализации Jackson. Параметры конструктора могут трактоваться ей как поля сериализационной формы.
До появления флага -parameters Jackson использовал для этого аннотации. Каждый параметр снабжался аннотацией @JsonProperty. Значением аннотации передавалась строка, которая повторяла имя параметра. Эти аннотации затем можно было получить, вызвав Method#getParameterAnnotations().
Java Guru🤓 #java
🔥9👍5❤2
Как создать объект в обход конструктора?
Этот пост – не рекомендация. Описанное здесь крайне редко имеет основание к применению (если имеет вообще).
Десериализация – полагается, что сериализационная форма уже была когда-то сконструирована, теперь же экземпляр просто восстанавливается с помощью метода readObject. Вызывается конструктор ближайшего не serializable родителя.
Внутри десериализация использует ReflectionFactory.newConstructorForSerialization из пакета sun.reflect – метод создания объекта заданного типа с указанным (возможно родительским) конструктором. Можно использовать его напрямую. Конструктор всё же вызывается, но это может быть конструктор класса Object.
Unsafe.allocateInstance – непосредственно создает экземпляр не вызывая конструктор, ничего лишнего.
Оба способа позволяют создать объект, не вызван ни конструктор, ни инициализаторы полей. Все члены остаются со значениями по-умолчанию (null, 0, false). Однако для final поля дефолтным значением считается указанное в инициализации, оно и будет установлено.
Пользуясь этими грязными хаками, помните: пакеты sun.* никогда не были частью официально поддерживаемого API Java, и есть не во всех версиях Java.
Java Guru🤓 #java
Этот пост – не рекомендация. Описанное здесь крайне редко имеет основание к применению (если имеет вообще).
Десериализация – полагается, что сериализационная форма уже была когда-то сконструирована, теперь же экземпляр просто восстанавливается с помощью метода readObject. Вызывается конструктор ближайшего не serializable родителя.
Внутри десериализация использует ReflectionFactory.newConstructorForSerialization из пакета sun.reflect – метод создания объекта заданного типа с указанным (возможно родительским) конструктором. Можно использовать его напрямую. Конструктор всё же вызывается, но это может быть конструктор класса Object.
Unsafe.allocateInstance – непосредственно создает экземпляр не вызывая конструктор, ничего лишнего.
Оба способа позволяют создать объект, не вызван ни конструктор, ни инициализаторы полей. Все члены остаются со значениями по-умолчанию (null, 0, false). Однако для final поля дефолтным значением считается указанное в инициализации, оно и будет установлено.
Пользуясь этими грязными хаками, помните: пакеты sun.* никогда не были частью официально поддерживаемого API Java, и есть не во всех версиях Java.
Java Guru🤓 #java
👍6❤4🔥4
Что такое classpath?
Classpath – это параметр, который указывает приложениям где искать пользовательские классы. По этому адресу должны быть найдены все классы, для которых не применяются специальные загрузчики. На место поиска стандартных классов JRE этот параметр не влияет.
Кроме непосредственно Java-приложений (команда java), этот параметр применим и для других утилит JDK, таких как javac, javadoc и другие.
Есть два основных способа установки classpath: в переменной окружения ОС CLASSPATH, и в аргументе командной строки -cp (синоним -classpath). Второй способ предпочтительнее, потому что позволяет устанавливать разные значения для разных приложений. Значение по умолчанию – текущая директория.
В параметре передаются пути к jar-файлам и корневым директориям с пакетами. Пути разделяют символом : в параметре командной строки, или же ; в переменной окружения. Чтобы включить все файлы директории, разрешается использовать в конце пути символ *.
Если приложение запускается из jar-файла (java -jar), classpath должен быть указан в его манифесте.
Java Guru🤓 #java
Classpath – это параметр, который указывает приложениям где искать пользовательские классы. По этому адресу должны быть найдены все классы, для которых не применяются специальные загрузчики. На место поиска стандартных классов JRE этот параметр не влияет.
Кроме непосредственно Java-приложений (команда java), этот параметр применим и для других утилит JDK, таких как javac, javadoc и другие.
Есть два основных способа установки classpath: в переменной окружения ОС CLASSPATH, и в аргументе командной строки -cp (синоним -classpath). Второй способ предпочтительнее, потому что позволяет устанавливать разные значения для разных приложений. Значение по умолчанию – текущая директория.
В параметре передаются пути к jar-файлам и корневым директориям с пакетами. Пути разделяют символом : в параметре командной строки, или же ; в переменной окружения. Чтобы включить все файлы директории, разрешается использовать в конце пути символ *.
Если приложение запускается из jar-файла (java -jar), classpath должен быть указан в его манифесте.
Java Guru🤓 #java
🔥14👍5❤2
Как прочитать байткод?
Байткод – аналог машинного кода для JVM. Он получается в результате компиляции исходного .java файла и хранится в .class файле. Анализ байткода иногда может помочь в исследовании бага, или лучше настроить производительность. Он необходим для отладки с инструментами динамической манипуляции байткодом, вроде ASM или BCEL.
Это бинарный код, и открыть его простым текстовым редактором не выйдет. В пакете утилит JDK доступен инструмент javap. Это утилита командной строки для чтения .class файлов.
Аргументами передается полное имя класса, и classpath в котором этот класс искать. По умолчанию отображаются только объявления публичных членов. Ключ -p добавит приватные методы и поля; -v выведет дополнительные метаданные; -c отобразит сам байткод – скомпилированную реализацию методов.
Java Guru🤓 #java
Байткод – аналог машинного кода для JVM. Он получается в результате компиляции исходного .java файла и хранится в .class файле. Анализ байткода иногда может помочь в исследовании бага, или лучше настроить производительность. Он необходим для отладки с инструментами динамической манипуляции байткодом, вроде ASM или BCEL.
Это бинарный код, и открыть его простым текстовым редактором не выйдет. В пакете утилит JDK доступен инструмент javap. Это утилита командной строки для чтения .class файлов.
Аргументами передается полное имя класса, и classpath в котором этот класс искать. По умолчанию отображаются только объявления публичных членов. Ключ -p добавит приватные методы и поля; -v выведет дополнительные метаданные; -c отобразит сам байткод – скомпилированную реализацию методов.
Java Guru🤓 #java
👍14🔥5❤3
От чего может случиться NoSuchMethodError?
Как очевидно из названия, программа пытается вызвать метод, которого не существует. В большинстве случаев это приводит к ошибке еще на этапе компиляции. Но в некоторых ситуациях о проблеме становится известно только во время выполнения, и тогда выбрасывается это исключение.
Вариантов таких ситуаций два. Либо проект был изменен и частично перекомпилирован; либо программа несовместима с внешней зависимостью: например неправильная версия jar-библиотеки в classpath.
Исключение NoSuchMethodError наследуется от LinkageError. Все такие ошибки – признаки несовместимых изменений после компиляции класса.
Не следует путать эту ошибку с NoSuchMethodException – исключением, которое случается при попытке вызвать несуществующий метод с помощью Reflection API. Важное отличие в том, что это не Error, его можно ловить и обрабатывать.
Java Guru🤓 #java
Как очевидно из названия, программа пытается вызвать метод, которого не существует. В большинстве случаев это приводит к ошибке еще на этапе компиляции. Но в некоторых ситуациях о проблеме становится известно только во время выполнения, и тогда выбрасывается это исключение.
Вариантов таких ситуаций два. Либо проект был изменен и частично перекомпилирован; либо программа несовместима с внешней зависимостью: например неправильная версия jar-библиотеки в classpath.
Исключение NoSuchMethodError наследуется от LinkageError. Все такие ошибки – признаки несовместимых изменений после компиляции класса.
Не следует путать эту ошибку с NoSuchMethodException – исключением, которое случается при попытке вызвать несуществующий метод с помощью Reflection API. Важное отличие в том, что это не Error, его можно ловить и обрабатывать.
Java Guru🤓 #java
🔥14👍3❤2
В чём разница между интерпретатором, AOT и JIT-компилятором?
Интерпретация – простое последовательное воспроизведение кода программы, команда за командой.
AOT-компиляция (ahead-of-time, статическая) – процесс превращения текста на языке программирования в нативный код на машинном языке. Так работают языки вроде C++. В современных JDK можно получить настоящий ahead-of-time скомпилированный машинный код с помощью утилиты jaotc.
JIT-компиляция (just-in-time, динамическая) – «умная» интерпретация. Среда выполнения анализирует исполняемый код, оптимизируя часто вызываемые участки. Таким способом программа работает значительно быстрее, и сохраняет при этом преимущества платформо-независимости оригинального кода. Именно с JIT-компиляцией связана необходимость «прогрева» программ перед тестированием производительности.
Эти термины относятся не только к JVM, но и ко множеству других языков программирования. Конкретно в Java байткод – интерпретируемый. Но в JVM по умолчанию работает JIT-компилятор. А процесс компиляции Java-кода в байткод можно назвать AOT-компиляцией.
Для взаимодействия с JIT-компилятором из кода в JDK поставляется класс java.lang.Compiler. Его методом disable() можно отключить JIT и перевести программу в режим простой интерпретации. Сейчас этот класс объявлен устаревшим и готовится к удалению.
Более красивый способ влиять на компилятор – передавать его настройки параметрами JVM. Параметр -Djava.compiler=NONE также переключит программу с JIT на интерпретатор. В теории, через этот же параметр можно подключить другой JIT-компилятор, альтернативный встроенному в JVM.
Java Guru🤓 #java
Интерпретация – простое последовательное воспроизведение кода программы, команда за командой.
AOT-компиляция (ahead-of-time, статическая) – процесс превращения текста на языке программирования в нативный код на машинном языке. Так работают языки вроде C++. В современных JDK можно получить настоящий ahead-of-time скомпилированный машинный код с помощью утилиты jaotc.
JIT-компиляция (just-in-time, динамическая) – «умная» интерпретация. Среда выполнения анализирует исполняемый код, оптимизируя часто вызываемые участки. Таким способом программа работает значительно быстрее, и сохраняет при этом преимущества платформо-независимости оригинального кода. Именно с JIT-компиляцией связана необходимость «прогрева» программ перед тестированием производительности.
Эти термины относятся не только к JVM, но и ко множеству других языков программирования. Конкретно в Java байткод – интерпретируемый. Но в JVM по умолчанию работает JIT-компилятор. А процесс компиляции Java-кода в байткод можно назвать AOT-компиляцией.
Для взаимодействия с JIT-компилятором из кода в JDK поставляется класс java.lang.Compiler. Его методом disable() можно отключить JIT и перевести программу в режим простой интерпретации. Сейчас этот класс объявлен устаревшим и готовится к удалению.
Более красивый способ влиять на компилятор – передавать его настройки параметрами JVM. Параметр -Djava.compiler=NONE также переключит программу с JIT на интерпретатор. В теории, через этот же параметр можно подключить другой JIT-компилятор, альтернативный встроенному в JVM.
Java Guru🤓 #java
🔥8❤7👍3
Чем отличается interface от @interface?
Среди интерфейсов выделяется особая группа, которая не объявляет никаких методов. Пример такого интерфейса – Serializable. Такие интерфейсы добавляют классу некую семантику, которая позже используется либо с помощью рефлексии (и instanceof), либо вообще не программно, а как информация для разработчиков и инструментов разработки. Это маркерные интерфейсы. Маркерный интерфейс представляет метаинформацию класса.
Начиная с Java 1.5 в языке появился новый вид типов – аннотации. Они берут на себя и расширяют возможности маркерного интерфейса:
1. Можно применять аннотацию не только к классу или интерфейсу, но почти к чему угодно: к пакетам, к методам, их параметрам, переменным. Полный список представлен в перечислении ElementType;
2. Аннотация может нести данные в своих элементах
3. Аннотация может не присутствовать в рантайме, или даже остаться только в исходнике, не попав в байткод вовсе. Определяется ее RetentionPolicy;
4. Можно сделать аннотацию не наследуемой, просто не помечая ее @Inherited;
5. И конечно же, синтаксис. Примененная аннотация с первого взгляда отличается от настоящих интерфейсов.
Joshua Block в главе 37 Effective Java выделяет два преимущества маркерных интерфейсов перед аннотациями на этапе компиляции:
1. Можно требовать использование только маркированного параметра, так как маркерный интерфейс – это еще и тип;
2. Можно сузить применяемость маркера к только определенным типам, сделав интерфейс их наследником.
Возвращаясь к вопросу, ключевое слово @interface объявляет аннотацию, interface – интерфейс.
В результате компиляции в .class-файле аннотация превращается в интерфейс-наследник java.lang.annotation.Annotation, помеченный флагом ACC_ANNOTATION. Элементы превращаются в абстрактные методы. Этим объясняется синтаксис объявления. Специфичные для аннотаций атрибуты описаны в JVMS 4.7.16-4.7.22.
К слову, конструкции вида @something в javadoc называются тэгами. Они выглядят похоже на аннотации, также представляют метаинформацию для документации, но технически не имеют с ними ничего общего.
Java Guru🤓 #java
Среди интерфейсов выделяется особая группа, которая не объявляет никаких методов. Пример такого интерфейса – Serializable. Такие интерфейсы добавляют классу некую семантику, которая позже используется либо с помощью рефлексии (и instanceof), либо вообще не программно, а как информация для разработчиков и инструментов разработки. Это маркерные интерфейсы. Маркерный интерфейс представляет метаинформацию класса.
Начиная с Java 1.5 в языке появился новый вид типов – аннотации. Они берут на себя и расширяют возможности маркерного интерфейса:
1. Можно применять аннотацию не только к классу или интерфейсу, но почти к чему угодно: к пакетам, к методам, их параметрам, переменным. Полный список представлен в перечислении ElementType;
2. Аннотация может нести данные в своих элементах
3. Аннотация может не присутствовать в рантайме, или даже остаться только в исходнике, не попав в байткод вовсе. Определяется ее RetentionPolicy;
4. Можно сделать аннотацию не наследуемой, просто не помечая ее @Inherited;
5. И конечно же, синтаксис. Примененная аннотация с первого взгляда отличается от настоящих интерфейсов.
Joshua Block в главе 37 Effective Java выделяет два преимущества маркерных интерфейсов перед аннотациями на этапе компиляции:
1. Можно требовать использование только маркированного параметра, так как маркерный интерфейс – это еще и тип;
2. Можно сузить применяемость маркера к только определенным типам, сделав интерфейс их наследником.
Возвращаясь к вопросу, ключевое слово @interface объявляет аннотацию, interface – интерфейс.
В результате компиляции в .class-файле аннотация превращается в интерфейс-наследник java.lang.annotation.Annotation, помеченный флагом ACC_ANNOTATION. Элементы превращаются в абстрактные методы. Этим объясняется синтаксис объявления. Специфичные для аннотаций атрибуты описаны в JVMS 4.7.16-4.7.22.
К слову, конструкции вида @something в javadoc называются тэгами. Они выглядят похоже на аннотации, также представляют метаинформацию для документации, но технически не имеют с ними ничего общего.
Java Guru🤓 #java
❤8👍7🔥3❤🔥1🕊1
Для чего используются аннотации?
Удобно рассмотреть случаи применения аннотаций с точки зрения возможных значений их свойства RetentionPolicy:
SOURCE – аннотация присутствует только в исходном коде, но не вовлечена в компиляцию. Можно разделить их на две категории:
Первая – аннотации для программиста, а не для программы. Это всевозможные маркеры. Они добавляют аннотируемым элементам некоторую специальную семантику. Более формализованный вариант документации. Примеры – @Immutable и @ThreadSafe из Hibernate.
Вторая категория – инструкции для инструментов разработки. Примеры этой категории, @SuppressWarnings и @Override могут влиять на предупреждения и ошибки компиляции. IntelliJ IDEA умеет понимать @Nullable и @NonNull из Spring Framework, и предупреждать о возможных NullPointerException.
CLASS – самое экзотическое, но при том стандартное значение. Аннотация попадает в байткод .class-файла, но игнорируется загрузчиком классов. В результате такая аннотация недоступна для рефлекшна. Используется для сторонних инструментов, обрабатывающих байткод, например для обфускаторов.
RUNTIME – самое ходовое значение. Цель снабжается метаинформацией, доступной во время выполнения программы. Сама по себе аннотация всё так же не добавляет нового поведения. Для практической пользы runtime-аннотации в программе должен быть исполнен некоторый код процессинга, который прочитает метаинформацию инструментами Reflection API. Такой механизм широко используется во множестве популярных фреймворков: Spring, Hibernate, Jackson.
Java Guru🤓 #java
Удобно рассмотреть случаи применения аннотаций с точки зрения возможных значений их свойства RetentionPolicy:
SOURCE – аннотация присутствует только в исходном коде, но не вовлечена в компиляцию. Можно разделить их на две категории:
Первая – аннотации для программиста, а не для программы. Это всевозможные маркеры. Они добавляют аннотируемым элементам некоторую специальную семантику. Более формализованный вариант документации. Примеры – @Immutable и @ThreadSafe из Hibernate.
Вторая категория – инструкции для инструментов разработки. Примеры этой категории, @SuppressWarnings и @Override могут влиять на предупреждения и ошибки компиляции. IntelliJ IDEA умеет понимать @Nullable и @NonNull из Spring Framework, и предупреждать о возможных NullPointerException.
CLASS – самое экзотическое, но при том стандартное значение. Аннотация попадает в байткод .class-файла, но игнорируется загрузчиком классов. В результате такая аннотация недоступна для рефлекшна. Используется для сторонних инструментов, обрабатывающих байткод, например для обфускаторов.
RUNTIME – самое ходовое значение. Цель снабжается метаинформацией, доступной во время выполнения программы. Сама по себе аннотация всё так же не добавляет нового поведения. Для практической пользы runtime-аннотации в программе должен быть исполнен некоторый код процессинга, который прочитает метаинформацию инструментами Reflection API. Такой механизм широко используется во множестве популярных фреймворков: Spring, Hibernate, Jackson.
Java Guru🤓 #java
❤10👍6🔥3
Можно ли добавить одному элементу несколько одинаковых аннотаций?
По умолчанию нельзя. До Java 8 нужно было создавать дополнительную аннотацию-контейнер, в ней в виде проперти объявлять массив интересующих аннотаций. При применении набор аннотаций нужно было оборачивать в этот контейнер (см. на картинке).
Начиная с Java 8 в стандартную библиотеку добавлена мета-аннотация @Repeatable.
Механизм ее действия такой же, как раньше: помечая ей интересующую аннотацию, необходимо указать параметром @Repeatable аннотацию-контейнер. Нововведение заключается в синтаксисе использования: теперь набор аннотаций оборачивается в контейнер неявно.
Со стороны Reflection при чтении мета-информации тоже появилась возможность не оперировать контейнером явно – метод getAnnotationsByType при необходимости найдет и развернет этот контейнер.
Java Guru🤓 #java
По умолчанию нельзя. До Java 8 нужно было создавать дополнительную аннотацию-контейнер, в ней в виде проперти объявлять массив интересующих аннотаций. При применении набор аннотаций нужно было оборачивать в этот контейнер (см. на картинке).
Начиная с Java 8 в стандартную библиотеку добавлена мета-аннотация @Repeatable.
Механизм ее действия такой же, как раньше: помечая ей интересующую аннотацию, необходимо указать параметром @Repeatable аннотацию-контейнер. Нововведение заключается в синтаксисе использования: теперь набор аннотаций оборачивается в контейнер неявно.
Со стороны Reflection при чтении мета-информации тоже появилась возможность не оперировать контейнером явно – метод getAnnotationsByType при необходимости найдет и развернет этот контейнер.
Java Guru🤓 #java
👍9❤4🔥4
Что такое Type Erasure?
Компилятор удаляет из байткода класс-файла информацию о типах-дженериках. Этот процесс и называется стирание типов (type erasure). Он появился в Java 5 вместе с самими дженериками. Такое решение позволило сохранить обратную совместимость без перекомпилляции кода Java 4.
Стирание состоит из трех действий:
🟢 Если параметры ограничены (bounded), вместо типа-параметра в местах использования подставляется верхняя граница, иначе Object;
🟢 В местах присвоения значения типа-параметра в переменную обычного типа добавляется каст к этому типу;
🟢 Генерируются bridge-методы.
Информация о типах стирается только из методов и полей, но остается в метаинформации самого класса. Получить эту информацию в рантайме можно с помощью рефлекшна, методом Field#getGenericType.
Тип со стертой информацией о дженериках называется «Non-reifiable».
Стирание типов позволяет не создавать при применении дженериков новые классы, в отличие от, например, шаблонов C++.
Java Guru🤓 #java
Компилятор удаляет из байткода класс-файла информацию о типах-дженериках. Этот процесс и называется стирание типов (type erasure). Он появился в Java 5 вместе с самими дженериками. Такое решение позволило сохранить обратную совместимость без перекомпилляции кода Java 4.
Стирание состоит из трех действий:
Информация о типах стирается только из методов и полей, но остается в метаинформации самого класса. Получить эту информацию в рантайме можно с помощью рефлекшна, методом Field#getGenericType.
Тип со стертой информацией о дженериках называется «Non-reifiable».
Стирание типов позволяет не создавать при применении дженериков новые классы, в отличие от, например, шаблонов C++.
Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥3❤1
Как ограничивается тип generic параметра?
В объявлении дженерик-параметра класса или метода может быть указана его верхняя граница (bound)
Ключевое слово extends применяется как для классов, так и для интерфейсов. Фактическим параметром такого класса Foo может быть или сам Number, или его наследники.
Помимо ограничения возможных применяемых типов, bounded-параметр дает право использовать в реализации методы и поля типа-ограничителя – он будет как минимум предком фактического типа. Это достигается стиранием типа-параметра до верхней границы.
Тип-параметр может иметь несколько верхних границ, то есть границу-пересечение типов: <T extends Comparable & Serializable>. Стирание произойдет до первой из границ, остальные послужат только ограничением вариантов фактического типа. Поэтому граница-класс, при наличии, должна быть указана раньше границ-интерфейсов.
При указании значения дженерик-параметра переменной может быть использован вайлдкард – символ ?. Вайлдкард значит, что мы не собираемся использовать информацию о конкретном типе, этот тип может быть любым. Это не то же самое, что не указать дженерик параметр совсем.
Для вайлдкарда также как и для объявления типа-параметра можно обозначить верхнюю границу. Но в отличие от объявления здесь нельзя использовать пересечение типов, по крайней мере пока.
Кроме того в случае вайлдкарда можно задать нижнюю границу
Означает, что мы не будем использовать информацию о конкретном типе, но будем знать что это предок класса Number. То есть или сам Number, или Object.
В объявлении класса или метода использование super запрещено, так как не имеет смысла.
Разобраться в использования ограниченных вайлдкардов поможет это видео.
Хороший API должен уметь эффективно работать с классами-наследниками, то есть быть ко- или контравариантным где это необходимо. При этом без bounded вайлдкардов не обойтись. Чтобы запомнить, какая граница нужна в каких случаях, Joshua Bloch предложил мнемонику PECS: Producer-extends, Consumer-super
Java Guru🤓 #java
В объявлении дженерик-параметра класса или метода может быть указана его верхняя граница (bound)
class Foo<T extends Number>
Ключевое слово extends применяется как для классов, так и для интерфейсов. Фактическим параметром такого класса Foo может быть или сам Number, или его наследники.
Помимо ограничения возможных применяемых типов, bounded-параметр дает право использовать в реализации методы и поля типа-ограничителя – он будет как минимум предком фактического типа. Это достигается стиранием типа-параметра до верхней границы.
Тип-параметр может иметь несколько верхних границ, то есть границу-пересечение типов: <T extends Comparable & Serializable>. Стирание произойдет до первой из границ, остальные послужат только ограничением вариантов фактического типа. Поэтому граница-класс, при наличии, должна быть указана раньше границ-интерфейсов.
При указании значения дженерик-параметра переменной может быть использован вайлдкард – символ ?. Вайлдкард значит, что мы не собираемся использовать информацию о конкретном типе, этот тип может быть любым. Это не то же самое, что не указать дженерик параметр совсем.
Для вайлдкарда также как и для объявления типа-параметра можно обозначить верхнюю границу. Но в отличие от объявления здесь нельзя использовать пересечение типов, по крайней мере пока.
Кроме того в случае вайлдкарда можно задать нижнюю границу
Foo<? super Number> foo;
Означает, что мы не будем использовать информацию о конкретном типе, но будем знать что это предок класса Number. То есть или сам Number, или Object.
В объявлении класса или метода использование super запрещено, так как не имеет смысла.
Разобраться в использования ограниченных вайлдкардов поможет это видео.
Хороший API должен уметь эффективно работать с классами-наследниками, то есть быть ко- или контравариантным где это необходимо. При этом без bounded вайлдкардов не обойтись. Чтобы запомнить, какая граница нужна в каких случаях, Joshua Bloch предложил мнемонику PECS: Producer-extends, Consumer-super
Java Guru🤓 #java
👍7❤4🔥3
Что такое ковариантность и контравариантность?
Формально, ковариантность/контравариантность типов – это сохранение/обращение порядка наследования для производных типов. Проще говоря, когда у ковариантных сущностей типами-параметрами являются родитель и наследник, они сами становятся как бы родителем и наследником. Контравариантные наоборот, становятся наследником и родителем.
Легче всего осознать эти понятия на примерах:
🟢 Ковариантность: List<Integer> можно присвоить в переменную типа List<? extends Number> (как будто он наследник List<Number>).
🟢 Контравариантность: в качестве параметра метода List<Number>#sort типа Comparator<? super Number> может быть передан Comparator<Object> (как будто он родитель Comparator<Number>)
Отношение типов «можно присвоить» – не совсем наследование, такие типы называются совместимыми (отношение «is a»).
Существует еще одно связанное понятие – инвариантность. Инвариантность – это отсутствие свойств ковариантности и контрвариантности. Дженерики без вайлдкардов инвариантны: List<Number> нельзя положить ни в переменную типа List<Double>, ни в List<Object>.
Массивы ковариантны: в переменную Object[] можно присвоить значение типа String[].
Переопределение методов начиная с Java 5 ковариантно относительно типа результата и типов исключений.
Java Guru🤓 #java
Формально, ковариантность/контравариантность типов – это сохранение/обращение порядка наследования для производных типов. Проще говоря, когда у ковариантных сущностей типами-параметрами являются родитель и наследник, они сами становятся как бы родителем и наследником. Контравариантные наоборот, становятся наследником и родителем.
Легче всего осознать эти понятия на примерах:
Отношение типов «можно присвоить» – не совсем наследование, такие типы называются совместимыми (отношение «is a»).
Существует еще одно связанное понятие – инвариантность. Инвариантность – это отсутствие свойств ковариантности и контрвариантности. Дженерики без вайлдкардов инвариантны: List<Number> нельзя положить ни в переменную типа List<Double>, ни в List<Object>.
Массивы ковариантны: в переменную Object[] можно присвоить значение типа String[].
Переопределение методов начиная с Java 5 ковариантно относительно типа результата и типов исключений.
Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍4❤2