Java Interview Review
6.72K subscribers
12 photos
262 links
Популярные вопросы и ответы с собеседований на Java-разработчика.

Канал для Android-разработчиков @AndroidSobes

Связаться с нами @SobesAdmin

http://itsobes.ru/
Download Telegram
to view and join the conversation
Как сгенерировать уникальный идентификатор?

UUID (universally unique identifier) – стандарт, описывающий способ создания правильных уникальных идентификаторов. Значения генерируются на основании таких источников информации, как системное время и MAC-адрес, за счет чего они остаются с достаточной вероятностью уникальными, даже будучи сгенерированными независимо. Можно с разных машин добавлять в базу данных записи с UUID-идентификаторами, и не бояться конфликта.

UUID бывает пяти разных версий, версия определяет способ создания. Формат остается одинаковым: это строковое шестнадцатеричное представление 128-битного целого числа (два long-а), разделенное дефисами на группы фиксированного размера:

25b32eaa-3017-4ad7-9224-383f6bfa5212

В Java уникальный идентификатор представляется иммутабельным классом UUID из пакета java.util. В нём нет сложной логики, только getter-ы для описанных в стандарте составных частей, конструктор и статические фабричные методы.

Единственный конструктор позволяет создать экземпляр по двум указанным половинам значения (старшие и младшие 64 бита в виде long параметров). nameUUIDFromBytes строит из заданного массива байтов UUID версии 3. randomUUID генерирует случайный уникальный идентификатор версии 4, с применением SecureRandom.

#Классы
Отличается ли List<?> от List<? extends Object>?

Все классы без исключения наследуются от Object. Поэтому неограниченный wildcard <?> всегда подразумевает его в качестве верхней границы. Оба этих типа в рантайме сотрутся в List<Object>, функциональных отличий нет.

Не смотря на одинаковое поведение, существует одно синтаксическое различие. Неограниченный дженерик – reifiable тип. Это значит, что он представлен в рантайме. Такой тип можно использовать в операторе instanceof, тогда как синтаксическая конструкция x instanceof List<? extends Object> приведет к ошибке компиляции.

Тип List без параметра имеет больше отличий, мы уже говорили о них ранее, в публикациях про raw types и проблему heap pollution.

#Дженерики
Как измерить производительность метода?

Если вы собираетесь оптимизировать код, или же выбрать лучшее с точки зрения производительности готовое решение, выбор необходимо подтвердить тестом производительности до и после. Такие тесты называются benchmark.

Первый вариант который приходит в голову – измерить время до, выполнить метод, посмотреть сколько времени прошло. У такого подхода есть ряд нюансов. Каждый последующий замер может случайным образом сильно отличаться от предыдущего. В реальном приложении результат будет совсем отличным от тестового, потому что виртуальная машина будет «прогретой»: заполнятся различные системные кэши, произойдут JIT-оптимизации горячего кода, память наполнится мусором.

Наивный подход может показать приблизительную производительность долгих операций: ввода-вывода, системных вызовов, или пользовательских сценариев на развернутом тестовом стенде. При тестировании кусков Java кода (в микробенчмарках) время выполнения очень мало, поэтому упомянутые нюансы вносят значительный вклад в результат.

Самое правильное решение – использовать готовый инструмент из JDK, JMH. Для этого его библиотеки добавляются в зависимости, а тестовый метод помечается аннотацией @Benchmark. Тесты можно запускать в виде исполняемого jar-файла, либо вызовом специальных программных методов библиотеки. Итоги тестирования будут выведены в виде таблицы в консоль.

Для дальнейшего погружения в особенности использования JMH рекомендуется к просмотру доклад автора инструмента.

#Инструменты
В чём преимущества и недостатки Spring Boot?

Основные сущности фреймворка Spring Boot – это стартеры. Зависимости с названиями вида spring-boot-starter-xxx выполняют две основных задачи. Во-первых, они добавляют набор типичных сторонних библиотек-зависимостей; во-вторых, регистрируют типичные бины и их конфигурации. Кроме того, со Spring Boot в проекте появляется ряд таких полезностей, как embedded-сервер, конфигурация web-приложения без web.xml, метрики, properties вынесенные из кода во внешние файлы.

Например, spring-boot-starter-data-jpa даст вам готовый комплект всего необходимого для использования JPA: драйвер, совместимую с ним версию Hibernate, библиотеки Persistence API и Spring Data. В контексте приложения появятся все нужные для JPA репозиториев бины.

Таким образом Spring Boot ускоряет и упрощает разработку, дает возможность избавиться от boilerplate-кода в проекте и сфокусироваться на бизнес-задачах. Это бывает особенно важно в микросервисной архитектуре, когда создается большое количество приложений.

С другой стороны, такая избыточность естественно приводит к большей тяжеловесности и медлительности приложения.

#Spring
Какие бывают проблемы с арифметикой в Java?

Переполнения.
Числа примитивных типов в Java хранятся в дискретной оперативной памяти компьютера и занимают фиксированный объем. Из этого вытекает ограничение диапазона возможных значений. Когда результат арифметической операции выпадает из диапазона, значение идет по кругу – максимальное становится минимальным, либо наоборот. Такая ситуация называется переполнение (underflow/overflow).

Решение: если опасность переполнения значима, помогут методы с суффиксом *Exact из классе Math. Это безопасные аналоги арифметических операций, которые бросают исключение в случае переполнения.

Платформо-зависимые округления.
По умолчанию JVM производит арифметические вычисления насколько это возможно точно. Пределы точности могут зависеть от аппаратного обеспечения. Это неприемлемо для программ, к которым предъявляют строгие требования переносимости, когда результат вычислений должен быть одним и тем же на любом железе.

Решение: модификатор strictfp в объявлении класса или метода приводит точность вычислений к единой спецификации IEEE 754. За это может ухудшиться производительность и уменьшиться точность значений.

ArithmeticException.
Операторы могут выбрасывать исключение. Это происходит, например, при делении на ноль. Это же исключение бросают безопасные методы из Math.

Решение: неожиданное исключение обычно указывает на логическую ошибку. Лучший способ предотвратить логические ошибки – покрыть код Unit-тестами.

#Язык
Как прочитать байткод?

Байткод – аналог машинного кода для JVM. Он получается в результате компиляции исходного .java файла и хранится в .class файле. Анализ байткода иногда может помочь в исследовании бага, или лучше настроить производительность. Он необходим для отладки с инструментами динамической манипуляции байткодом, вроде ASM или BCEL.

Это бинарный код, и открыть его простым текстовым редактором не выйдет. В пакете утилит JDK доступен инструмент javap. Это утилита командной строки для чтения .class файлов.

Аргументами передается полное имя класса, и classpath в котором этот класс искать. По умолчанию отображаются только объявления публичных членов. Ключ -p добавит приватные методы и поля; -v выведет дополнительные метаданные; -c отобразит сам байткод – скомпилированную реализацию методов.

Для более детального изучения синтаксиса рекомендуется статья на хабре.

#JVM
#Инструменты
А вот и мы – прямиком из небольшого отпуска, всем привет!

Из поездок возвращаются с сувенирами, а мы вернулись с новостями. Первая новость: мы запустили Патреон! Некоторые из вас спрашивали про него, и мы решили – почему бы и нет? Для нас это хороший способ получить обратную связь, а для вас – сказать спасибо тем, кто работает над каналами.

Вторая новость: теперь, помимо Android и Java, у нас есть @ITSobes с вопросами для любого IT-собеседования. Computer sceince, владение компьютером, знания о работе информационных систем – мы готовы отвечать на любые вопросы.

Stay tuned,
Команда ITSobes🤗
От чего случается UnsupportedClassVersionError?

Одна из отличительных особенностей языка Java – обратная совместимость версий. Это значит что код, который был написан на Java 5, будет прекрасно работать и на Java 11.

К сожалению, это не работает в обратную сторону. Невозможно поддержать фичи, которые еще не были придуманы.

Скомпилированный .class файл хранит в себе версию байткода x.y – версию формата файла. Минорная часть y версии отражает совместимые технические изменения в рамках той же самой версии Java. Мажорная часть x увеличивается в каждом следующем релизе. Версии формата для разных релизов:

Java SE 14 = 58
Java SE 13 = 57
Java SE 12 = 56
Java SE 11 = 55
Java SE 10 = 54
Java SE 9 = 53
Java SE 8 = 52
Java SE 7 = 51
Java SE 6.0 = 50
Java SE 5.0 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45

Ошибка UnsupportedClassVersionError возникает, когда JVM пытается загрузить слишком новую версию класса. Чтобы получить байткод, совместимый со старыми JVM, при компиляции необходимо передать версию в параметре --release (до Java 8 в параметрах -source и -target).

#JVM
Приведите примеры использования fork/join framework

Как следует из названий связанных классов, ForkJoinPool используется для рекурсивных задач. Это такие задачи, которые можно делить на порции, подзадачи. Отделение подзадачи – это операция fork, финальная агрегация результатов подзадач – join.

Реализация fork/join для самых популярных общих случаев уже есть в стандартной библиотеке, работать непосредственно с классом ForkJoinPool не потребуется. Метод parallelSetAll из класса Arrays применяет fork/join для генерации элементов массива; parallelPrefix для модификации; parallelSort для сортировки.

Фреймворк неявно работает и в параллельных стримах. В этом случае логику fork определяет его сплитератор, а join выполняют потоковые операции. Классический пример:

Arrays.stream(new int[]{1, 2, 3, 4}).parallel().sum();


Существуют целые категории частных задач, решения которых хорошо параллелизуются: векторные операции, работа с графами, поиск данных. Для специфичных задач придется реализовывать собственные RecursiveTask, RecursiveAction, или Spliterator.

#Многопоточность
Что такое Hibernate?

Hibernate – популярная библиотека, которая реализует технологию JPA, обеспечивает объектно-реляционный маппинг (ORM).

По задумке она избавляет разработчика от проблем хранения объектно-ориентированных (инкапсуляция-полиморфизм-наследование) данных в реляционных таблицах.

Среди особенностей можно выделить такие, как HQL (объектно-ориентированный SQL), автоматическую генерацию таблиц, два уровня кэширования, независимость от конкретной базы данных.

На сегодняшний день Hibernate считается многими разработчиками отчасти устаревшей. На больших проектах она требует объемный набор XML-конфигураций, на практике зачастую приходится спускаться на уровень нативного SQL конкретной базы, а маппинг порой ведет себя неочевидно. Для более близкого знакомства с проблемами этой библиотеки рекомендуется доклад Николая Алименкова.

Несмотря на свои проблемы, Hibernate остается лучшей альтернативой самописным DAO. Её использует под капотом Spring Data.

Список возможных альтернатив можно найти на википедии.

#SQL
От чего может случиться NoSuchMethodError?

Как очевидно из названия, программа пытается вызвать метод, которого не существует. В большинстве случаев это приводит к ошибке еще на этапе компиляции. Но в некоторых ситуациях о проблеме становится известно только во время выполнения, и тогда выбрасывается это исключение.

Вариантов таких ситуаций два. Либо проект был изменен и частично перекомпилирован; либо программа несовместима с внешней зависимостью: например неправильная версия jar-библиотеки в classpath.

Исключение NoSuchMethodError наследуется от LinkageError. Все такие ошибки – признаки несовместимых изменений после компиляции класса.

Не следует путать эту ошибку с NoSuchMethodException – исключением, которое случается при попытке вызвать несуществующий метод с помощью Reflection API. Важное отличие в том, что это не Error, его можно ловить и обрабатывать.

#JVM
Как реализовать двусторонний обмен данными между потоками?

Вопрос, который зачастую дается в виде практической задачи. Конечно, результата можно добиться разными способами: парой атомарных переменных, критическими секциями, потокобезопасными коллекциями. Но полезно знать, что специально для этого случая в стандартной библиотеке java.util.concurrent есть простой класс Exchanger.

Класс содержит единственный метод V exchange(V x). Один поток передает в него данные, и встает в ожидание. Ожидание завершается, когда второй поток также приходит в метод exchange со своей порцией информации. В качестве результата вызова потоки получают данные друг друга.

На основе класса Exchanger удобно создавать пайплайны обработки данных. Первый поток выполняет свою часть обработки, и складывает результаты в буфер. В качестве буфера может работать любой многоразовый объект-контейнер. Когда он заполняется, следующий поток обменивает его на второй, пустой буфер. Таким образом два буфера используются поочередно, не выделяется лишний раз память и не нагружается GC. Далее из попарно обменивающихся буферами потоков может строиться длинная многопоточная цепочка обработки.

#Многопоточность
В чём разница между интерпретатором, 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.

#JVM
TestNG или JUnit – что выбрать?

Ответ на такого рода вопросы всегда зависит от дополнительных деталей контекста. Не просто так TestNG и JUnit – два самых популярных фреймворка для unit-тестирования, у каждого есть свои плюсы.

JUnit – золотой стандарт. Это библиотека из семейства xUnit, которое во многом сформировало unit-тестирование таким, каким мы знаем его сегодня. И до сих пор, оставаясь самой популярной библиотекой для тестирования, она продолжает активно развиваться.

Фреймворк TestNG был вдохновлен JUnit, но добавляет небольшой набор дополнительных фич. Некоторые из указанных на сайте возможностей на самом деле доступны и в JUnit: например data-driven тесты, или параллельное выполнение. Подробное сравнение JUnit 4 и TestNG доступно в статье от mkyong.

Стоит отметить, что современный JUnit 5 имеет достаточно заметные отличия от JUnit 4, их можно рассматривать как разные фреймворки. Подробнее об отличиях читайте на хабре.

#Инструменты
Чем IllegalArgumentException лучше чем NullPointerException?

Речь здесь идет о выборе подхода к обработке ошибки, когда пользователь передал в non-nullable параметр значение null.

Технически, оба этих исключения unchecked, оба из стандартной библиотеки, и особой разницы нет.

Однако семантически эти исключения отличаются. NullPointerException говорит пользователю о попытке обратиться к членам класса через null-ссылку. Это лишь техническое описание, без информации для пользователя о логике программы. IllegalArgumentException, напротив, явно говорит о недопустимом значении аргумента – это понятная для пользователя информация.

Семантическая разница иногда проявляется и технически. Например, в обработчиках исключений некоторых фреймворков именно IllegalArgumentException превращается в HTTP-ответ с кодом 400 Bad Request, в то время как NPE остается общим кодом «неизвестной ошибки» 500 Internal Server Error.

Кроме того, чтобы выбросить NullPointerException не требуется явного кода обработки null. Остается неизвестным, ожидаемо ли в этом случае исключение, или разработчик попросту забыл добавить корректную обработку.

Явная обработка null и выброс IllegalArgumentException для пользователя кода будет служить документацией, а сообщение об ошибке в аргументе его конструктора внесет еще больше ясности.

#Классы
Что такое phase, goal и lifecycle в Maven? (1/2)

Phase – виртуальные шаги из, которых состоит lifecycle в Maven. Вообще, существует три жизненных цикла:

• Clean – фазы pre-clean, clean, post-clean;
• Default – validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy;
• Site – pre-site, site, post-site, site-deploy.

Goal – это конкретное выполняемое плагином действие. Плагин привязывает свои голы к фазам. Например, когда мы вызываем mvn clean, работу по удалению файлов сборки делает не сама фаза clean, а привязанная к ней цель clean:clean из встроенного maven-clean-plugin.

#Инструменты
Что такое phase, goal и lifecycle в Maven? (2/2)

Когда аргументом команды mvn передается фаза, кроме нее самой выполнятся все предшествующие ей в том же лайфсайкле. Вместо фазы возможно явно передать цель (mvn clean:clean вместо mvn clean), но тогда никакие другие цели вызваны не будут (в данном примере цели фазы pre-clean).

Фазы всегда выполняются в том порядке, в котором они следуют в жизненном цикле. Если к одной фазе привязано несколько целей, они отработают в порядке объявления в pom.xml.

Некоторые плагины могут предоставлять цели, не привязанные ни к каким фазам. Их можно вызвать только явно.

Если в команду mvn передается несколько фаз/целей, они выполнятся последовательно. Каждая цель в процессе выполняется только однажды.

Так, вызов mvn test package – то же самое что mvn package, потому что в первом случае все цели из фазы test (и предыдущих) уже будут исполнены и пропущены в package. mvn clean install так заменить не получится, потому что это фазы из разных жизненных циклов.

#Инструменты
Отличаются ли сокращенные и обычные операторы?

Java предлагает программисту сокращенную запись для применения операции с сохранением ответа в операнд. Это например +=, &=, и другие. Их правильное название – операторы сложного присваивания (compound assignment). Сокращенные версии есть для всех арифметических и битовых операторов.

У таких сокращений есть одно неочевидное отличие от полных версий. Если прочитать спецификацию, там сказано, что x += y – это на самом деле сокращение от x = (XType)(x + y). То есть, кроме самой операции происходит приведение результата к типу левого операнда.

Незнание этой особенности может привести к ошибочно успешной компиляции, и неожиданным результатам работы кода (как в примере на изображении).

#Язык
4 и 5 декабря пройдет бесплатная Java конференция jLove – мероприятие будет интересно Java-программистам любого уровня от новичков до экспертов.

Конференция собрала лучших звезд Java мира из компаний Red Hat, IBM, Microsoft, jFrog, JetBrains, Oracle, Confluent, VMware и многих других.

Конференция пройдет онлайн, и мы хотим пригласить всех желающих принять участие – регистрация бесплатная!
Для полного погружения в конференцию будет пространство для общения со спикерами после выступлений, нетворкинг с участниками в Spatial Chat, общение со спонсорами, афтепати, и даже возможность выиграть книги по Reactive Spring или годовую подписку от JetBrains.

Все, что вам нужно знать на сайте: https://jlove.konfy.care.
Увидимся 4 и 5 декабря!
Сгенерируйте случайное число в интервале

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

Самые стандартные классы-генераторы случайных чисел создают равномерно распределенные значения. Любое число возникает с одинаковой вероятностью – это ожидаемое поведение для большинства задач. Например, метод Random.nextInt(1) будет генерировать примерно одинаковое количество значений 0 и 1.

Однако, программист легко может "испортить" равномерность значений последующими операциями. Возьмем вместо предыдущего примера Random.nextInt(2)%2. Такая конструкция тоже будет возвращать 0 или 1. Однако, третье возможное значение из генератора, 2, будет тоже превращено в 0. Значит, ответ 0 будет возникать в два раза чаще чем 1.

Это та причина, по которой лучше не пользоваться общепринятым «школьным» арифметическим ограничением с помощью оператора %. Вместо этого следует оставить заботу о распределении разработчикам библиотеки, и пользоваться в прикладном коде готовыми методами с границами.

Если задача подразумевает более плотную работу с распределениями, стоит воспользоваться специализированной библиотекой вроде Apache Commons Math.

#Классы
Лишает ли var строгой типизации?

Спасибо подписчикам, которые недавно подняли этот вопрос в чате-обсуждении.

Ключевое слово var появилось в Java 10. Указание var вместо типа локальной переменной применяет к ней механизм вывода типов (type inference). Тип будет вычислен на этапе компиляции из того, чем переменная инициализируется.

Отсюда несколько выводов. Во-первых, нельзя использовать var в полях класса, параметрах метода, и где-либо еще кроме локальных переменных. Во-вторых, обязана быть инициализация с понятным типом – варианты var x; или var x = null; не скомпилируются.

И главное следствие – к концу компиляции у таких переменных фиксированный и известный тип, который не может быть изменен позднее. А это и есть определение строгой типизации.

Ответ: нет, выводимый тип – строгий. Более того, типизация остается статической.

Главное упущение – в инициализации разрешено использовать diamond operator. В обычных обстоятельствах в нём выведется правильный generic-тип, но в случае var информации недостаточно, и типом-параметром будет Object.

#Язык