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

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

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

http://itsobes.ru/
Download Telegram
to view and join the conversation
Когда нужно использовать raw types?

Сначала вспомним, что такое raw type. В Java так называют generic-типы без указания типа-параметра. Такая языковая конструкция валидна, но в большинстве случаев приводит к предупреждению компилятора.

Предупреждение связано с риском получения проблемы heap pollution. Ей мы уже посвящали публикации ранее. Использование raw types никогда не оправдано – спецификация языка явно говорит: их поддержка остается только для обратной совместимости.

Есть всего три случая, когда использовать обобщенный тип без параметра правильно:
• Целевая версия Java < 5.0 (2002 год и ранее – вряд ли это ваш случай);
• В литерале класса. List<String>.class не сработает, нужно писать List.class;
• В операторе instanceof. Вместо instanceof Set<Integer> должно быть instanceof Set.

#Язык
#Дженерики
Как в Java разобрать JSON?

JSON – на ряду с XML, самый популярный текстовый формат передачи данных. Существует великое множество библиотек для работы с ним, здесь перечислены самые популярные.

Gson – решение от Google. В простом случае, максимально просто в применении. Популярно в Android.

org.json – Простое решение для работы с атрибутами из JSON-строки. Не умеет сериализовать из объекта в JSON и обратно.

Jackson – библиотека, знакомая всем любителям Spring Framework. Широкий простор конфигурации. Может использоваться в Java EE (в составе JAX-RS), хорошо подходит для сложных web-приложений.

JSONP – один из компонентов Java EE. Есть как стриминговая версия (экономит память при больших документах), так и обычная (когда нужно работать с документом целиком). Можно взять реализацию Glassfish, или любого другого сервера приложений. Решение «от производителя», как часто бывает, не самое популярное.

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

#Сериализация
Прежде чем выбрать куда собеседоваться, стоит почитать в интернете отзывы об этом месте работы:
Forwarded from Java Developer
Мюнхен. Facebook. Java

Собрал все посты, в которых описана работа в разных компаниях

• Старт карьеры в Custis t.me/java_developer/374
• Работа в Мюнхене t.me/java_developer/146
• Работа в EPAM t.me/java_developer/363
• Работа в Nexign t.me/java_developer/534
• Сингапурский Deutsche Bank t.me/java_developer/417
• Разраб из Facebook t.me/java_developer/355
Как создать пару публичный/приватный ключ?

Не следует путать генерацию ключей с задачей их чтения, о которой здесь писали ранее.

Для генерации ключей используется KeyPairGenerator. Архитектурно он похож на читающую ключи KeyFactory: реализации для разных алгоритмов также поставляются через SPI, доступ к ним также осуществляется через getInstance.

Перед использованием, генератор нужно инициализировать. Для этого методом initialize устанавливаются три опциональных параметра:
• Желаемый размер ключа;
• Источник случайных значений типа SecureRandom;
• Специфичные для алгоритма настройки в объекте подкласса AlgorithmParameterSpec.

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

Сам метод генерации generateKeyPair не требует параметров. Его можно вызывать повторно для генерации нескольких разных пар. Результат его вызова – объект KeyPair, который просто хранит под уже знакомыми нам интерфейсами PrivateKey и PublicKey пару инстансов специфичных для алгоритма подклассов.

#Безопасность
Опишите жизненный цикл Spring Bean (1/2)

Beans – центральный объект заботы Spring Framework. За кулисами фреймворка с ними происходит множество процессов. Во многие из них можно вмешаться, добавив собственную логику в разные этапы жизненного цикла. Через следующие этапы проходит каждый отдельно взятый бин:

1. Инстанцирование объекта. Техническое начало жизни бина, работа конструктора его класса;

2. Установка свойств из конфигурации бина, внедрение зависимостей;

3. Нотификация aware-интерфейсов. BeanNameAware, BeanFactoryAware и другие. Мы уже писали о таких интерфейсах ранее. Технически, выполняется системными подтипами BeanPostProcessor, и совпадает с шагом 4;

4. Пре-инициализация – метод postProcessBeforeInitialization() интерфейса BeanPostProcessor;

5. Инициализация. Разные способы применяются в таком порядке:
Метод бина с аннотацией @PostConstruct из стандарта JSR-250 (рекомендуемый способ);
Метод afterPropertiesSet() бина под интерфейсом InitializingBean;
Init-метод. Для отдельного бина его имя устанавливается в параметре определения initMethod. В xml-конфигурации можно установить для всех бинов сразу, с помощью default-init-method;

6. Пост-инициализация – метод postProcessAfterInitialization() интерфейса BeanPostProcessor.

#Spring
Опишите жизненный цикл Spring Bean (2/2)

Когда IoC-контейнер завершает свою работу, мы можем кастомизировать этап штатного уничтожения бина. Как со всеми способами финализации в Java, при жестком выключении (kill -9) гарантии вызова этого этапа нет. Три альтернативных способа «деинициализации» вызываются в том же порядке, что симметричные им методы инициализации:

1. Метод с аннотацией @PreDestroy;
2. Метод с именем, которое указано в свойстве destroyMethod определния бина (или в глобальном default-destroy-method);
3. Метод destroy() интерфейса DisposableBean.

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

#Spring
Что будет со ссылкой на метод, если заменить объект-владельца?

Ответ на этот вопрос будет очевиден, если вы уверенно понимаете, что скрывается за терминами ссылки вообще и ссылки на метод.

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

Факт позднего связывания в этом вопросе может ввести в заблуждение. Связывание случается в момент обращения, а не вызова. В результате в переменной хранится неизменяемая копия ссылки на метод. Она ведет на метод объекта, а не хранящей его переменной. Поэтому переприсвоение переменной позже не окажет на ссылку никакого эффекта.

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

#Язык
Опишите жизненный цикл сервлета (1/2)

Совсем недавно мы рассмотрели жизненный цикл компонентов Spring-приложения. Поговорим теперь об этапах работы центрального элемента JavaEE web-приложения – сервлета.

События жизненного цикла сервлета можно разделить на две группы. Первая группа – события, относящиеся непосредственно к самому сервлету. Все они объявляются методами из базового интерфейса Servlet. Примерно такие же фазы жизни и у фильтров.

1. Загрузка класса и создание экземпляра сервлета, конструктор. Может быть создано несколько экземпляров, здесь можно располагать только техническую часть инициализации (подробнее).

2. Инициализация – в общем случае метод init. Инициализация гарантированно происходит до первого запроса, один раз на один сервлет (даже если экземпляров несколько). По умолчанию она ленивая, то есть случается непосредственно перед обработкой этого первого запроса. Другой режим, пре-инициализация, включается свойством loadOnStartup.

Если в качестве инициализации нужно всего лишь параметризовать сервлет, вместо реализации метода достаточно передать параметры в свойстве initParams аннотации @WebServlet. Эти параметры, и другую информацию можно прочитать из объекта конфигурации типа ServletConfig. Дефолтная реализация init-метода принимает эту конфигурацию и сохраняется в поле.

3. Обработка запроса – service. В отличие от Spring Framework, в классической JavaEE все запросы вне зависимости от HTTP-метода, пути и набора параметров попадают в единый метод-обработчик. Вся информация о запросе приходит в параметре ServletRequest. У метода нет возвращаемого значения, вместо этого модифицируется второй параметр – ServletResponse. Для фильтров аналогичный метод doFilter, он кроме запроса и ответа принимает третий параметр, FilterChain.

4. Завершение работы – destroy. Здесь всё как обычно с финализациями: вызывается только однажды, только при корректном завершении, после ее вызова service/doFilter уже никогда не будет вызван.

#JavaEE
Опишите жизненный цикл сервлета (2/2)

Другая группа событий – то что происходит со связанными с сервлетом сущностями. Таких сущностей три: ServletContext, ServletRequest и HttpSession.

На каждую из этих сущностей есть два интерфейса-слушателя. Интерфейс XListener сообщает о создании и уничтожении сущности через два соответствующих метода. Так, чтобы совершить какое-то действие при открытии новой HTTP-сессии, действие нужно поместить в реализацию метода HttpSessionListener.sessionCreated().

Все три типа позволяют хранить пользовательские данные – атрибуты. Они хранятся по ключам-строкам, как в хэш-таблице. Методы второго интерфейса XAttributeListener дают возможность обработать добавление, изменение и удаление атрибутов. То есть, если где-то в программе в контекст сервлета добавляется значение (вызван ServletContext.setAttribute("myKey", someValue)), то сработает ServletContextAttributeListener.attributeAdded.

• ServletContext – прослойка между отдельными сервлетами и сервлет-контейнером. Есть только один контекст на всё web-приложение. Он в отличие от сервлетов не ленивый, и инициализируется на старте приложения, до инициализации отдельных сервлетов.

• ServletRequest – отдельный запрос. На каждый запрос создается новый такой объект, инициализируется перед основным обработчиком Servlet.service(), уничтожается после.

• HttpSession – HTTP-сессия, которая служит для поддержания состояния между запросами для одного пользователя. Обычно создается вручную внутри обработчика Servlet.service(). Уничтожаться может как тоже вручную в обработчике, так и по истечению срока жизни. Только для сессии есть третий интерфейс, который уведомляет об изменении идентификатора – HttpSessionIdListener.

Чтобы объект под этими интерфейсами начал получать уведомления, его нужно зарегистрировать в контексте. Это делается либо явным вызовом ServletContext.addListener, либо аннотацией @WebListener на класс.

#JavaEE
Как обойти коллекцию?

for/while. Классический способ: целочисленная переменная-индекс, которая увеличивается от 0 до size(). Можно использовать для неполного обхода, с нестандартным шагом. Плата за это – возможность ошибиться в индексах и менее читабельный код.

Iterator. ООП-способ: методом iterator() получить объект-итератор, и вызывать у него next() пока hasNext() возвращает true. В реализации может быть дополнительная логика, такая как потокобезопасность. Такой «объект-итерацию» коллекции можно передать в сторонний код, не отдавая саму коллекцию. Всё еще требует слишком много кода.

for Iterable. Синтаксический сахар для обхода итератором. Простейший синтаксис когда нужен просто обход. В отличие от явного использования итератора не дает возможности модифицировать элементы в процессе.

Стримы. Создать от коллекции стрим и работать с элементами в нём. Кроме простого forEach(), можно воспользоваться всей мощью Java Steam API – фильтровать, преобразовывать и агрегировать элементы. За это создаются лишние объекты, а синтаксис гораздо более развесистый.

Функции Java 8. С этой версии появились удобные средства для обхода не только строк. У коллекций и хэш-таблиц добавились методы forEach для обхода и replaceAll для модификации. Как со стримами, они дают функциональный стиль, но без избыточного создания стримов. Внутри используются простые итераторы и циклы for.

#Коллекции
#Язык
Может ли имя класса не совпадать с именем файла?

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

Protected и private классов верхнего уровня не бывает в принципе, а вот на package-protected это ограничение не распространяется. Это значит, что класс без модификатора доступа может иметь любое имя. Также это значит, что рядом с основным публичным классом файла (или вместо него) можно объявить любое количество других классов без модификатора доступа, с произвольными именами. Они будут доступны внутри всего пакета.

Так что ответ – может.

#Язык
Что такое Jakarta EE?

Сомнительно, что такое спросят на собеседовании. Этот пост – скорее экскурс в историю и способ разобраться в хитросплетениях названий.

Enterprise-версия Java дважды за свою историю была переименована.

Говоря о первом переименовании, стоит сказать пару слов о версиях Java вообще. Сначала JDK подчинялся правилам версионирования semver, продукт с названием Java 1 – это JDK версии 1.0.2 и позже 1.1.

В 1.2 вся платформа получил маркетинговое название Java 2, а версия JDK разделилась на отдельные части: J2SE, J2ME и J2EE. Эти правила, сохранялись до 1.4.

В 2004 году, подгоняемая молодым конкурентом в лице C#, Java получила множество обновлений. Среди них – появление современной нумерации. Из названия следующего релиза пропала цифра 2, а формат версии из инженерного стал маркетинговым. Вышла Java 5, J2EE 1.5 превратилась в Java EE 5.

В 2018-ом компания Oracle рассталась с enterprise-версией, передала её open-source организации Eclipse Foundation. Авторское право на слово «Java» осталось за компанией, и технология получила свое нынешнее официальное название: Jakarta EE. Не следует путать ее с другим проектом Apache Jakarta.

Еще один забавный факт: до Java 7 у версий были кодовые имена, семерка вышла под именем «Дельфин». Далее разработка перешла от Sun к Oracle, и веселье кончилось.

#JavaEE
Как использовать JavaEE сервлет в Spring Framework?

Web-приложение на Spring MVC технически само по себе работает на сервлетах: всю обработку запросов берет на себя единый DispatcherServlet. С его помощью реализуется паттерн Front Controller.

Если вам нужно определить в программе полностью независимый от Spring-контекста сервлет или фильтр, ничего особенного для этого делать не нужно. Как обычно в Servlet API, нужно объявить класс, добавить его в web.xml как сервлет, добавить для сервлета маппинг.

Сервлет живет вне Spring-контекста, внедрение зависимостей в нём просто так не заработает. Чтобы использовать autowiring, на этапе инициализации сервлета вызывается статический SpringBeanAutowiringSupport.processInjectionBasedOnServletContext, с текущим сервлетом и его контекстом в аргументах. В этом же утилитарном классе есть ряд других средств для работы с контекстом извне.

Если программа построена на Spring Boot, создание бина типа ServletRegistrationBean поможет добавить сервлеты в рантайме. А для декларативного добавления на этапе компиляции, к классу конфигурации применяется @ServletComponentScan. С этой аннотацией стартер приложения просканирует и добавит в контекст все web-компоненты в стиле Servlet 3.0: классы с аннотациями @WebFilter, @WebListener и @WebServlet.

#JavaEE
#Spring
Как передать runtime информацию о generic-типе?

Когда вы проектируете API-метод библиотеки, иногда логика его реализации может зависеть от указанного клиентом типа. Особенно часто с этой задачей встречаются при разработке парсеров. Например, библиотека Jackson превращает JSON в объект заданного класса. На интервью этот вопрос можно встретить в виде практической задачи.

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

Решение, которое сработает для многих случаев – объявление в методе аргумента типа Class<T>. Пользователь будет передавать в него значение Foo.class или fooInstance.getClass(). Проблемы с ним начинаются, когда становится нужно передать generic-тип. Синтаксис .class не поддерживает дженерики, а .getClass() от экземпляров List<String> и List<Integer> вернет один и тот же объект-описание сырого типа List.

На помощь приходит техника, описанная в предыдущей публикации.

1. Объявляется generic класс-обертка над типом: TypeInformation<T>;. Наш метод будет принимать информацию о типе в виде экземпляра этой обертки.

2. В обертку добавляется конструктор с видимостью protected. Теперь можно создавать объекты только наследников, но не самого этого типа.

3. Пользователь будет передавать экземпляр анонимного наследника обертки: new TypeInformation<List<String>>() {}.

4. Внутри вызов getClass().getGenericSuperclass() вернет ParameterizedType. Это будет описание типа родителя анонима, то есть самой обертки. Из него с помощью getActualTypeArguments() можно достать рантайм-информацию о значении дженерика (о List<String>).

#Дженерики
Что такое fail-fast и fail-safe итераторы?

Это не какие-то отдельные типы, а характеристики разных реализаций интерфейса Iterator. Они определяют, как поведет себя итератор при изменении перебираемой последовательности.

Fail-fast – «быстрый» итератор. Когда после его создания коллекция как-либо изменилась, он падает с ошибкой без лишних разбирательств. Так работает итератор класса ArrayList, при изменении он выбрасывает ConcurrentModificationException. Рекомендуется не основывать логику программы на fail-fast отказах, и использовать их только как признак ошибки реализации.

Fail-safe – «умный» итератор. Обычно плата за отказоустойчивость – возможная неконсистентность данных («слабая консистентность»). Итератор класса ConcurrentHashMap работает с копией данных, он не выбросит исключение при изменении коллекции, но может не увидеть часть свежих изменений. Плата за отсутствие ошибок других fail-safe итераторов может отличаться, детали всегда можно найти в документации коллекций.

#Коллекции
Какой способ логирования выбрать?

Библиотеки логирования, ставшие стандартом де-факто, в хронологическом порядке их появления:

• java.util.Logging из стандартной библиотеки
• Log4j (deprecated)
• Logback
• Log4j 2

Все эти библиотеки поддерживают основные фичи: уровни логирования (что писать), различные хэндлеры (куда писать), форматы (как писать). Развитие библиотек происходило последовательно, в нюансах каждая из них учитывала ошибки предыдущей, сохраняя её преимущества. Обычно выбирают самые свежие варианты – Logback и Log4j 2.

Для совмещения нескольких решений в одном проекте существуют библиотеки-фасады, которые подставляют разные реализации под общий интерфейс:
• SLF4j
• Apache Commons Logging (JCL)

Кроме того, можно использовать bridge-библиотеки. Они маскируют одну реализацию непосредственно под интерфейс другой. С их помощью, например, можно уместить в одном проекте использование двух разных фасадов, или обойтись вовсе без фасадов.

Для более глубокого погружения в вопрос библиотек логирования рекомендуется к просмотру этот доклад.

#Инструменты
Как сравнивать элементы перечисления?

Элементы enum-а компилируются в статические константы-экземпляры его класса. Экземпляры гарантированно синглтоны. Это значит, для их сравнения безопасно использовать ==, даже после десериализации и в многопоточной среде.

Скомпилированный класс неявно наследуется от java.lang.Enum, в котором все методы из Object кроме toString объявлены финальными. В частности, невозможно изменить поведение метода equals – он сравнивает enum-ы с помощью ==. Так что equals тоже можно использовать без опаски.

Но помимо этого есть несколько отличий в пользу ==:

1. == не выбросит NullPointerException. Прежде чем вызывать equals у переменной, придется удостовериться что она не null.

2. == не позволит сравнить объекты разных типов. Оператор еще на этапе компиляции подскажет, что такое сравнение не имеет смысла. equals же будет принимать аргумент под типом Object, и всегда возвращать false уже в рантайме.

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

#Язык
Как остановить поток?

В Java поток представлен классом Thread. В нём есть метод stop(), но пользоваться им нельзя, метод помечен как deprecated. Такая жесткая остановка моментально возвращает все захваченные потоком мониторы, и защищенные ими данные могут оказаться в неконсистентном состоянии.

Разработчики рекомендуют вместо этого использовать флаг, который будет показывать о намерении остановить поток. Флаг выставляется извне потока, а внутри проверяется в подходящий момент. Если нужно остановиться, поток просто выходит из метода run(). В качестве такого флага подойдет переменная типа AtomicBoolean.

Когда в потоке используются блокирующие операции, обычно для определенного типа операции существует свой способ её прервать. Например, можно закрыть сокет, на котором поток ожидает. Для большинства блокирующих операций сработает метод Thread.interrupt(). С его помощью можно прервать Object.wait() и операции из NIO.

Останется только правильно обработать такое прерывание. Прерванный wait() выбросит InterruptedException, Selector.select() вернет результат. Чтобы отличить осознанное прерывание с целью завершить тред от какого-либо другого, его обработку всё ещё необходимо снабдить проверкой флага.

#Многопоточность
Какой выбрать тип для даты/времени?

В пакете java.util расположены старые классы стандартной библиотеки Java: Date (дата+время), Calendar (конвертация и манипуляция), TimeZone (смещение часового пояса). Эти классы обладали рядом известных проблем. Экземпляры были изменяемыми, что делало их потоко-небезопасными. Работа с датами через календарь была неудобной, не было нормальной поддержки часовых поясов и интернационализации.

Постепенно стандартом де-факто стала сторонняя библиотека Joda-Time. Её разработчики решили все названные выше проблемы.

В Java 8 был добавлен пакет java.time, который взял решения из Joda-Time в стандарт, создатель библиотеки участвовал в разработке. Ключевые классы пакета:
LocalDate, LocalTime и LocalDateTime – локальные для пользователя дата/время.
ZonedDateTime – дата/время в определенной часовой зоне.
Period и Duration – периоды дат и времени соответственно.

Отдельно существуют классы Date и Time пакета java.sql. Это представление даты и времени для обмена данными через JDBC. Не стоит пользоваться ими вне уровня доступа к данным, хотя бы потому, что это классы-наследники старого java.util.Date.

Таким образом, в проектах на Java 8+ нужно использовать современные java.time.*, для более старых – подключать Joda-Time.

#Классы
Из каких основных сущностей состоит Spring-приложение? (1/2)

Bean – объект бизнес-логики в терминологии Spring Framework.

BeanDefinition – описание того, как создавать бин. Объект хранит его тип, метаинформацию, набор параметров для конструктора.

BeanFactory – главная точка входа в DI-контейнер. Хранит BeanDefinition-ы, умеет создавать по ним экземпляры бинов, или выдавать существующие, в зависимости от скоупа.

BeanPostProcessor – донастраивает только что созданные бины, перед тем как положить их в контейнер. Его методы уже упоминались в публикации про жизненный цикл. Типичное место, чтобы оборачивать бины в прокси. Также с помощью такого пост-процессора внедряются @Autowired-зависимости. Пост-процессоры бинов живут внутри экземпляра BeanFactory.

BeanFactoryPostProcessor – тоже пост-обработчик, но для определений бинов (BeanDefinition). Обычно используется для модификации параметров или класса, из которых будут строиться бины.

Для создания определений бинов в основном применяются классы и интерфейсы *BeanDefinitionReader. Некоторые из них вызываются прямо из контекста приложения, другие реализуют BeanFactoryPostProcessor. Один такой пост-процессор, например, отвечает за добавление определений бинов по аннотациям @Component и @Configuration.

#Spring