Опишите синтаксис javadoc-комментария (2/2)
Комментарий состоит из двух частей: описание и блок тегов. Первый блок содержит всю информацию в свободной форме. Во втором находятся теги (ранее уже упоминали их). Каждый тег начинается с новой строки, через пробел за ним следует значение.
Один тег можно использовать в блоке описания –
Среди всех тегов обязательными считаются только
•
•
•
•
•
Теги
#Инструменты
Комментарий состоит из двух частей: описание и блок тегов. Первый блок содержит всю информацию в свободной форме. Во втором находятся теги (ранее уже упоминали их). Каждый тег начинается с новой строки, через пробел за ним следует значение.
Один тег можно использовать в блоке описания –
@link
. Он не обязан быть на новой строке, обрамляется фигурными скобками, и при рендеринге превращается в <a>
со ссылкой на другую страницу документации.Среди всех тегов обязательными считаются только
@param
для каждого параметра метода, и @return
для не-void методов. Они применимы только для методов. А теги @author
и @version
наоборот, используются только в документации классов. Остальные блочные теги можно использовать везде: •
@deprecated
•
@exception
(то же что @throws
)•
@see
•
@since
•
@serial
(то же что @serialField
или @serialData
)Теги
@author
, @param
, @throws
и @see
могут входить в один комментарий в нескольких экземплярах.#Инструменты
Под каким типом хранить период времени?
В стандартной библиотеке современных версий Java для этих целей есть два класса:
Оба класса реализуют общий интерфейс
Экземпляры обоих классов могут быть созданы из значений отдельных компонентов, из двух моментов времени методом between, или из строки. Строковое представление Duration:
До Java 8 основным способом хранения периода были числовые примитивы. В этом подходе есть много недостатков, среди которых в первую очередь неограниченность значений и ненаглядность. Чтобы в Java 8+ получить период числом, используется метод
#Классы
В стандартной библиотеке современных версий Java для этих целей есть два класса:
Period
– календарный период. Количество дней, месяцев и лет. Одним днем здесь считается день в терминах ZonedDateTime
.Duration
– длительность времени. Количество наносекунд, секунд, минут, часов, и тоже дней. Здесь один день – ровно 24 часа.Оба класса реализуют общий интерфейс
TemporalAmount
– период времени вообще. Оба иммутабельны, и как следствие, потокобезопасны. Любая модифицирующая операция вроде plusX()
возвращает новый экземпляр с измененным значением.Экземпляры обоих классов могут быть созданы из значений отдельных компонентов, из двух моментов времени методом between, или из строки. Строковое представление Duration:
"P2DT3H4M"
, Period: "P1Y2M3D"
.До Java 8 основным способом хранения периода были числовые примитивы. В этом подходе есть много недостатков, среди которых в первую очередь неограниченность значений и ненаглядность. Чтобы в Java 8+ получить период числом, используется метод
between()
нужного элемента енама ChronoUnit
.#Классы
Как работает инъекция прототипа в синглтон?
Раньше мы уже рассматривали различия скоупов singleton и prototype в Spring Framework. Допустим ситуацию, когда в singleton-компонент внедряется зависимость со скоупом prototype – когда будет создан её объект?
Если просто добавить к определению бина аннотацию
Примитивный способ получать новый объект при каждом обращении – отказаться от
Воспользоваться автоматическим внедрением зависимостей можно через внедрение метода (паттерн «Команда»). Автовайрится не сам объект, а производящий его метод.
Более красивый декларативный способ – правильно настроить определение бина. В аннотации
Лучший способ разобраться со скоупами – прочитать официальный гайд с иллюстрациями, и поэкспериментировать на практике. Для начала попробуйте пример с изображения ниже.
#Spring
Раньше мы уже рассматривали различия скоупов singleton и prototype в Spring Framework. Допустим ситуацию, когда в singleton-компонент внедряется зависимость со скоупом prototype – когда будет создан её объект?
Если просто добавить к определению бина аннотацию
@Scope(SCOPE_PROTOTYPE)
, и использовать этот бин в синглтоне через аннотацию @Autowired
– будет создан только один объект. Потому что синглтон создается только однажды, и обращение к прототипу случится тоже однажды при его создании (при внедрении зависимости).Примитивный способ получать новый объект при каждом обращении – отказаться от
@Autowired
, и доставать его из контекста вручную. Для этого нужно вызывать context.getBean(MyPrototype.class)
.Воспользоваться автоматическим внедрением зависимостей можно через внедрение метода (паттерн «Команда»). Автовайрится не сам объект, а производящий его метод.
Более красивый декларативный способ – правильно настроить определение бина. В аннотации
@Scope
кроме самого scopeName
доступен второй параметр – proxyMode
. По умолчанию его значение NO
– прокси не создается. Но если указать INTERFACES
или TARGET_CLASS
, то под @Autowired
будет внедряться не сам объект, а сгенерированный фреймворком прокси. И когда проксируемый бин имеет скоуп prototype
, то объект внутри прокси будет пересоздаваться при каждом обращении. Лучший способ разобраться со скоупами – прочитать официальный гайд с иллюстрациями, и поэкспериментировать на практике. Для начала попробуйте пример с изображения ниже.
#Spring
Как сделать код с арифметикой кроссплатформенным?
Степень платформонезависимости Java явно определяется её спецификацией. Согласно спецификации, когда мы работаем с числами с плавающей точкой, на продакшне результат арифметических вычислений может отличаться от локального. Это не всегда проблема, но об этом стоит помнить.
Во-первых, разберемся насколько сильно результатам позволено расходиться. В документации разных методов из
Если объявленной точности не хватает, на помощь придет класс
Для обычных языковых выражений вроде инициализаторов и операторов введено понятие свойства FP-strict. Выражения с этим свойством будут кроссплатформенным. Чтобы добавить свойство всем операторам, на методе используется модификатор
#Язык
Степень платформонезависимости Java явно определяется её спецификацией. Согласно спецификации, когда мы работаем с числами с плавающей точкой, на продакшне результат арифметических вычислений может отличаться от локального. Это не всегда проблема, но об этом стоит помнить.
Во-первых, разберемся насколько сильно результатам позволено расходиться. В документации разных методов из
Math
эта величина выражается в единицах ulp (unit in the last place). Это то, насколько увеличится число, если его битовое выражение увеличить на 1 бит. Для разных чисел значение ulp будет различаться. Получить его можно методом Math.ulp(x)
.Если объявленной точности не хватает, на помощь придет класс
StrictMath
. В нём находится примерно такой же набор инструментов как в Math
, но с повторяемыми результатами, которые можно получить стандартизованными алгоритмами на C.Для обычных языковых выражений вроде инициализаторов и операторов введено понятие свойства FP-strict. Выражения с этим свойством будут кроссплатформенным. Чтобы добавить свойство всем операторам, на методе используется модификатор
strictfp
(о котором мы уже упоминали).#Язык
Что такое Java-модули?
Java Platform Module System (JPMS) – система модулей, появившаяся в Java версии 9. Она разрабатывалась под названием Project Jigsaw, и ожидалась еще с Java 7.
Модуль – группа связанных пакетов и ресурсов. Механизм модулей определяет видимость содержимого одних модулей для других. Скрытые модули недоступны ни для обычного использования в коде, ни даже для рефлекшна:
Вдобавок, модули инкапсулируют собственные ресурсы – конфиги и медиафайлы больше не хранятся в общей куче.
Модуль определяется файлом объявления
JVM может работать относительно модулей в трех режимах:
• Legacy mode – применяется, когда код компилируется в режиме совместимости с версиями Java 8 и меньше. Весь код работает как безымянный модуль, флаги для настройки модулярности не работают.
• Single module mode – когда не используется флаг
• Multi-module mode – каждый модуль находится в собственной директории. Общая корневая директория передается в
#Модули
Java Platform Module System (JPMS) – система модулей, появившаяся в Java версии 9. Она разрабатывалась под названием Project Jigsaw, и ожидалась еще с Java 7.
Модуль – группа связанных пакетов и ресурсов. Механизм модулей определяет видимость содержимого одних модулей для других. Скрытые модули недоступны ни для обычного использования в коде, ни даже для рефлекшна:
Class.forName()
выбросит ClassNotFoundException
.Вдобавок, модули инкапсулируют собственные ресурсы – конфиги и медиафайлы больше не хранятся в общей куче.
Модуль определяется файлом объявления
module-info.java
. Поговорим о его содержимом и о применении модулей вообще в будущих постах постах.JVM может работать относительно модулей в трех режимах:
• Legacy mode – применяется, когда код компилируется в режиме совместимости с версиями Java 8 и меньше. Весь код работает как безымянный модуль, флаги для настройки модулярности не работают.
• Single module mode – когда не используется флаг
--module-source-path
. Структура проекта остается старой, файл module-info.java
лежит в директории /src
. Поэтому иметь несколько модулей в одном проекте не получится.• Multi-module mode – каждый модуль находится в собственной директории. Общая корневая директория передается в
--module-source-path
. Полноценное использование JPMS.#Модули
Перечислите методы класса java.lang.Object
Этот вопрос используется, как способ начать разговор – по большинству методов можно уйти в обсуждении далеко вглубь. В первую очередь важно запомнить сигнатуры – не зная ответов на вопросы по этим методам, можно будет хотя бы рассуждать отталкиваясь от них. Также полезно открыть исходник и внимательно прочитать javadoc-документацию. Поступим как на интервью, и далее рассмотрим каждый из методов детально. Их список:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
#МетодыObject
Этот вопрос используется, как способ начать разговор – по большинству методов можно уйти в обсуждении далеко вглубь. В первую очередь важно запомнить сигнатуры – не зная ответов на вопросы по этим методам, можно будет хотя бы рассуждать отталкиваясь от них. Также полезно открыть исходник и внимательно прочитать javadoc-документацию. Поступим как на интервью, и далее рассмотрим каждый из методов детально. Их список:
1.
public final native Class<?> getClass()
2.
public native int hashCode()
3.
public boolean equals(Object obj)
4.
protected native Object clone() throws CloneNotSupportedException
5.
public String toString()
6.
public final native void notify()
7.
public final native void notifyAll()
8.
public final native void wait(long timeout) throws InterruptedException
9.
public final void wait(long timeout, int nanos) throws InterruptedException
10.
public final void wait() throws InterruptedException
11.
protected void finalize() throws Throwable
#МетодыObject
getClass
Возвращает класс этого экземпляра. То есть результатом вызова
Подробнее об этом читайте на хабре.
#МетодыObject
Возвращает класс этого экземпляра. То есть результатом вызова
.getClass()
переменной типа Foo
может быть как Foo.class
, так и .class
любого из его подклассов. Компилятор страхуется от ClassCastException
в рантайме подменой возвращаемого типа метода на Class<? extends Foo>
.Подробнее об этом читайте на хабре.
#МетодыObject
equals, hashCode
Эти два метода придуманы для использования в Java Collections Framework и связаны общим контрактом, для соблюдения которого переопределять их необходимо вместе. Методы обязательно нужно переопределить чтобы эффективно использовать экземпляры как ключи в HashMap или
Контракт:
1. Если объекты
2.
3. Ничто не может быть
4.
По умолчанию
Подробная инструкция по переопределению этих методов описана в Effective Java Item 9 (больше деталей о волшебном числе 31 здесь).
#МетодыObject
Эти два метода придуманы для использования в Java Collections Framework и связаны общим контрактом, для соблюдения которого переопределять их необходимо вместе. Методы обязательно нужно переопределить чтобы эффективно использовать экземпляры как ключи в HashMap или
HashSet
. HashMap
работает тем эффективнее, чем «лучше» распределение хэшей.Контракт:
1. Если объекты
equals
, у них должны быть одинаковые hashCode
(не обязательно наоборот – коллизии допустимы!)2.
equals
должен быть отношением эквивалентности3. Ничто не может быть
equals(null)
4.
equals
и hashCode
должны возвращать одни и те же значения для одного и того же объекта при каждом последующем вызове, даже если состояние объекта изменилось. Это делает реализацию для изменяемых (mutable) объектов крайне сложной. По умолчанию
equals
сравнивает на ==
. С умолчательным hashCode
дела обстоят интереснее: он зависит от реализации JVM, и может быть неожиданным. Например в OpenJDK 7 это случайное число. Подробная инструкция по переопределению этих методов описана в Effective Java Item 9 (больше деталей о волшебном числе 31 здесь).
#МетодыObject
clone
По умолчанию
По контракту клон должен быть другим объектом (
Альтернативы (многие считают что более удобные) метода
#МетодыObject
По умолчанию
protected
– потому что универсальной реализации нет, а вызов приведет к CloneNotSupportedException
. Нужно писать свою реализацию, делать при этом ее public
и добавлять классу интерфейс Cloneable. Подразумевается что этот метод делает «глубокое копирование», то есть поля-ссылки копи будут вести на копии полей оригинала. Это диктуется соглашением, по которому объект-клон не должен зависеть от оригинала. По контракту клон должен быть другим объектом (
!=
оригиналу). Рекомендуется, чтобы все классы иерархии реализовывали Cloneable
, реализация метода начиналась с super.clone()
(если родитель не Object
), а результат был equals
и имел тот же класс что и оригинал.Альтернативы (многие считают что более удобные) метода
clone
- конструктор копирования и паттерн factory method. Всё, что нужно знать о копировании объектов в Java можно найти в Effective Java Item 11.#МетодыObject
toString
Строковое представление экземпляра. По умолчанию возвращает
#МетодыObject
#Строки
Строковое представление экземпляра. По умолчанию возвращает
"ПолноеИмяКласса@хэшВ16тиричномВиде"
(например "java.lang.Object@1a23b4f"
). Часть после @
– не адрес в памяти, так что умолчательная реализация почти не несет практической пользы. Полезно добавлять нормальную реализацию даже если не необходимо в логике программы – поможет в отладке. Готовый вызов x.toString()
с проверкой на null
уже реализован в String.valueOf(x)
.#МетодыObject
#Строки
finalize
Метод придуман для минимизации риска утечки внешних ресурсов. Может быть вызван виртуальной машиной при сборке мусора (добавляя при этом для нее избыточную нагрузку). Это не то же самое, что деструктор в C++.
Есть только гарантии, что метод не будет вызван пока есть ссылки на объект, и что не будет вызван больше одного раза. Даже то, что вызов будет вообще – не факт. Исполнять будет неизвестно какой, но не синхронизированный поток. Исключения проигнорируются.
С давних пор использовать финализаторы не рекомендуется (Effective Java Item 7), а с Java 9 этот метод помечен как deprecated. Вместо финализатора всегда стоит воспользоваться
#МетодыObject
Метод придуман для минимизации риска утечки внешних ресурсов. Может быть вызван виртуальной машиной при сборке мусора (добавляя при этом для нее избыточную нагрузку). Это не то же самое, что деструктор в C++.
Есть только гарантии, что метод не будет вызван пока есть ссылки на объект, и что не будет вызван больше одного раза. Даже то, что вызов будет вообще – не факт. Исполнять будет неизвестно какой, но не синхронизированный поток. Исключения проигнорируются.
С давних пор использовать финализаторы не рекомендуется (Effective Java Item 7), а с Java 9 этот метод помечен как deprecated. Вместо финализатора всегда стоит воспользоваться
try
/finally
, try-with-resource или более специализированными классами пакета java.lang.ref
.#МетодыObject
wait, notify, notifyAll
Часто этот вопрос формулируется как задача Producer-сonsumer. Эту задачу и практические задачи на многопоточность вообще при возможности лучше реализовывать на высокоуровневых примитивах синхронизации. Другой подход – воспользоваться также низкоуровневой, но оптимистической блокировкой на compareAndSet. Но обычно использование
Эти методы вместе с
Чтобы вызывать эти методы у объекта, необходимо чтобы был захвачен его монитор (т.е. нужно быть внутри synchronized-блока на этом объекте). В противном случае будет выброшено
Вызов
В теории, ожидание wait может быть прервано без вызова
Еще два нештатных случая завершения
Различные проблемы реализации блокировок рассмотрены в Java Concurrency in Practice 14.1.3, 14.2. Для желающих разобраться, как блокировки работают в кишках JVM, написана статья на хабре.
#МетодыObject
#Многопоточность
Часто этот вопрос формулируется как задача Producer-сonsumer. Эту задачу и практические задачи на многопоточность вообще при возможности лучше реализовывать на высокоуровневых примитивах синхронизации. Другой подход – воспользоваться также низкоуровневой, но оптимистической блокировкой на compareAndSet. Но обычно использование
notify
/wait
(пессимистическая блокировка) – условие этого задания, то есть требуется реализовать уже существую BlockingQueue.Эти методы вместе с
synchronized
– самый низкий уровень пессимистических блокировок в Java, использующийся внутри реализации примитивов синхронизации. Еще с Java 5 в непосредственном использовании этих методов нет необходимости, но теоретические знания всё еще часто спрашивают на интервью.Чтобы вызывать эти методы у объекта, необходимо чтобы был захвачен его монитор (т.е. нужно быть внутри synchronized-блока на этом объекте). В противном случае будет выброшено
IllegalMonitorStateException
. Так что для полного ответа нужно понимать, как работает monitor lock (блок synchronized
).Вызов
wait
тормозит текущий поток на ожидание на этом объекте и отпускает его монитор. Исполнение продолжится, когда другой поток вызовет notify
и отпустит блокировку монитора. Если на объекте ожидают несколько потоков, notify
разбудит один случайный, notifyAll
- все сразу.В теории, ожидание wait может быть прервано без вызова
notify
, по желанию JVM (spurious wakeup). На практике это бывает крайне редко, но нужно страховаться и после вызова wait
добавлять дополнительную проверку условия завершения ожидания.Еще два нештатных случая завершения
wait
– прерывание потока извне и таймаут ожидания. В случае прерывания выбрасывается InterruptedException
. Для таймаута нужно указать время ожидания параметрами метода wait
. Значение 0 проигнорируется.Различные проблемы реализации блокировок рассмотрены в Java Concurrency in Practice 14.1.3, 14.2. Для желающих разобраться, как блокировки работают в кишках JVM, написана статья на хабре.
#МетодыObject
#Многопоточность
Чем отличается final finally finalize? (1/2)
Тем, что это даже синтаксически разные вещи. Как и вопрос о методах Object, это способ начать разговор.
Когда в методе используется локальная переменная внешней области видимости, она обязана быть effectively final. В этом случае ключевое слово final необязательно, но значение всё равно не должно меняться.
#Язык
Тем, что это даже синтаксически разные вещи. Как и вопрос о методах Object, это способ начать разговор.
finalize
– метод-финализатор из Object
, мы уже рассмотрели в предыдущих постах.final
– модификатор, который применяется к переменным, полям, методам и классам. Переменная или поле становится неизменяемым и требует инициализации. Финальный метод нельзя переопределить в наследниках. Финальный класс не может иметь наследников вообще. Используется для создания хорошего (Effective Java Item 15) API по принципу наименьших привилегий.Когда в методе используется локальная переменная внешней области видимости, она обязана быть effectively final. В этом случае ключевое слово final необязательно, но значение всё равно не должно меняться.
#Язык
Чем отличается final finally finalize? (2/2)
Любое исключение, выброшенное из блока
После выполнится блок
Для требующих финализации классов («ресурсов») добавляется интерфейс
#Язык
finally
– часть языковой конструкции try-catch-finally.Любое исключение, выброшенное из блока
try
переводит исполнение в самый соответствующий ему catch
(при наличии). Этим продиктована необходимость располагать блоки catch
в строгом порядке, от типа исключения-наследника, к родителю. В случае multicatch тот же порядок должен соблюдаться и внутри одного catch. Больше примеров про порядок. После выполнится блок
finally
. Выполняется он в любом случае, было исключение или нет. Типичное использование – освобождение ресурсов, обязательные завершающие действия.Для требующих финализации классов («ресурсов») добавляется интерфейс
AutoCloseable
, повторяющийся код блока final
выносится в метод close
и вызывается неявно в конце try-with-resources. Если в этой конструкции присутствует и явный final
, он будет выполнен после.#Язык
Какие существуют литералы?
Литерал – последовательность символов, обозначающая значение примитивного типа (или строки).
🔘 Целочисленные (Integer). Возможные форматы:
🔘 С плавающей точкой (floating-point). Возможные форматы:
🔘 Символы и строки. Символ (char) – в одинарных кавычках:
🔘 Логические (boolean).
🔘 Специальный литерал
Все нюансы описаны в официальной документации.
#Язык
Литерал – последовательность символов, обозначающая значение примитивного типа (или строки).
🔘 Целочисленные (Integer). Возможные форматы:
2019
, 1__000_000
(с Java 7), 10048L
(можно l, но будет путаться с 1), 0xfd12aa
, 0b1011101
, 07654321
.🔘 С плавающей точкой (floating-point). Возможные форматы:
123.4
, 56.7e8
, .07
, 42F
, 1.4D
(избыточно, по умолчанию и так double).🔘 Символы и строки. Символ (char) – в одинарных кавычках:
'R'
. Спецсимволы пишутся с бэкслешем: '\n'
. Любой символ можно представлять в виде escape-последовательности: '\u00F1'
. Строковый литерал – последовательность символов в двойных кавычках: "Blabla"
. Для символов строки действуют те же правила.🔘 Логические (boolean).
true
и false
.🔘 Специальный литерал
null
.Все нюансы описаны в официальной документации.
#Язык
Какие бывают модификаторы?
🔘 Модификаторы доступа
🔘 Модификаторы для многопоточности
🔘
🔘 final
🔘
🔘
🔘
🔘
#Язык
🔘 Модификаторы доступа
private
, protected
, public
(рассмотрим подробнее в разделе #Классы)🔘 Модификаторы для многопоточности
synchronized
и volatile
(подробнее чуть позже)🔘
static
(рассмотрим подробнее в разделе #Классы)🔘 final
🔘
abstract
(рассмотрим подробнее в разделе #Классы)🔘
native
– реализация метода скрыта внутри JVM, нельзя указывать в пользовательском коде🔘
transient
– поле будет пропущено при сериализации🔘
strictfp
– самый экзотический, ограничивает точность вычисления для переменной с плавающей точкой до стандарта IEEE. Нужно для переносимости между платформами.#Язык
Чем отличаются checked и unchecked исключения?
Вопрос формулируют по-разному, суть вопроса – объяснение иерархии классов исключений. Подробно описано в документации.
Исключения бывают checked и unchecked. Checked требуется указывать в сигнатуре метода в разделе
🔘
🔘
🔘
🔘
#Язык
Вопрос формулируют по-разному, суть вопроса – объяснение иерархии классов исключений. Подробно описано в документации.
Исключения бывают checked и unchecked. Checked требуется указывать в сигнатуре метода в разделе
throws
; перехватывать или добавлять в throws
в вызывающем его методе. Unchecked можно добавить, но не обязательно, перехватывать не обязательно даже если указана в throws
. 🔘
Throwable
– базовый класс для всего что может быть использовано с оператором throw
и в конструкции try-catch🔘
RuntimeException
– «нормальные» unchecked-исключения🔘
Error
– unchecked исключения, которые означают «серьезные проблемы» приложения. Не должны обрабатываться (хотя технически можно). Теоретически JVM может находиться в невалидном состоянии и не давать больше никаких гарантий🔘
Exception
(кроме RuntimeException
) – checked исключения#Язык
Что такое synchronized?
Можно применять как модификатор метода, и как самостоятельный оператор с блоком кода. Выполняет код при захваченном мониторе объекта. В виде оператора объект указывается явно. В виде модификатора нестатического метода используется
Один из основных инструментов обеспечения потокобезопасности. Одновременно выполняется не более одного блока
Блок
#Язык
#Многопоточность
Можно применять как модификатор метода, и как самостоятельный оператор с блоком кода. Выполняет код при захваченном мониторе объекта. В виде оператора объект указывается явно. В виде модификатора нестатического метода используется
this
, статического – .class
текущего класса.Один из основных инструментов обеспечения потокобезопасности. Одновременно выполняется не более одного блока
synchronized
на одном и том же объекте. Такая блокировка называется intrinsic lock или monitor lock, подробно рассматривается в Java Concurrency in Practice 2.3.1.Блок
synchronized
также необходим для использования методов wait, notify, notifyAll.#Язык
#Многопоточность