Java Guru 🤓
13.2K subscribers
885 photos
16 videos
750 links
Канал с вопросами и задачами с собеседований!

По сотрудничеству и рекламе: @NadikaKir

Канал в перечне РКН: https://vk.cc/cJrSQZ

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Чем отличается final finally finalize?

Тем, что это даже синтаксически разные вещи. Как и вопрос о методах Object, это способ начать разговор.

finalize – метод-финализатор из Object.

final – модификатор, который применяется к переменным, полям, методам и классам. Переменная или поле становится неизменяемым и требует инициализации. Финальный метод нельзя переопределить в наследниках. Финальный класс не может иметь наследников вообще. Используется для создания хорошего API по принципу наименьших привилегий.

Когда в методе используется локальная переменная внешней области видимости, она обязана быть
effectively final. В этом случае ключевое слово final необязательно, но значение всё равно не должно меняться.

finally – часть языковой конструкции try-catch-finally.

Любое исключение, выброшенное из блока try переводит исполнение в самый соответствующий ему catch (при наличии). Этим продиктована необходимость располагать блоки catch в строгом порядке, от типа исключения-наследника, к родителю. В случае multicatch тот же порядок должен соблюдаться и внутри одного catch.
Больше примеров про порядок.

После выполнится блок finally. Выполняется он в любом случае, было исключение или нет. Типичное использование – освобождение ресурсов, обязательные завершающие действия.

Для требующих финализации классов («ресурсов») добавляется интерфейс AutoCloseable, повторяющийся код блока final выносится в метод close и вызывается неявно в конце
try-with-resources. Если в этой конструкции присутствует и явный final, он будет выполнен после.

Java Guru🤓 #java
6👍4🔥4
Чем отличаются checked и unchecked исключения?

Вопрос формулируют по-разному, суть вопроса – объяснение иерархии классов исключений. Подробно описано в документации.

Исключения бывают checked и unchecked. Checked требуется указывать в сигнатуре метода в разделе throws; перехватывать или добавлять в throws в вызывающем его методе. Unchecked можно добавить, но не обязательно, перехватывать не обязательно даже если указана в throws.

🔘Throwable – базовый класс для всего что может быть использовано с оператором throw и в конструкции try-catch
🔘RuntimeException – «нормальные» unchecked-исключения
🔘Error – unchecked исключения, которые означают «серьезные проблемы» приложения. Не должны обрабатываться (хотя технически можно). Теоретически JVM может находиться в невалидном состоянии и не давать больше никаких гарантий
🔘Exception (кроме RuntimeException) – checked исключения

@javatasks #java
Please open Telegram to view this post
VIEW IN TELEGRAM
5🔥4👍3
Зачем нужно ключевое слово default?

Изначально (с Java 1.5) это слово использовалось для объявления дефолтного значения элементов аннотации.

В Java 8 вместе с лямбдами и стримами появилась острая необходимость дополнить стандартные интерфейсы новыми методами. Никто естественно не собирался ломать обратную совместимость, и было предложено добавить методы по умолчанию.

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

В реализации такого метода его дефолтный вариант вызывается тем же синтаксисом, что и внешний класс из вложенного: InterfaceName.super.methodName().

Методы по умолчанию подошли еще на шаг к введению в Java беспроблемной версии множественного наследования – примесям (mixin). Интерфейс не может иметь состояния, поэтому полноценные примеси всё ещё недоступны.


@javatasks #java
👍7🔥41
В каком порядке указываются модификаторы?

Правила применения модификаторов поля описаны в JLS 8.3.1:
🔘Нельзя указывать один модификатор дважды;
🔘Нельзя использовать несколько модификаторов доступа одновременно.

Порядок указания вообще свободный, но обычно используется следующий:
@Аннотации, доступ,
static final transient volatile


Требования для модификаторов метода находятся в JLS 8.4.3:
🔘Те же требования, что и к полю;
🔘Совместно с abstract кроме аннотаций можно использовать только protected или public;
🔘native метод не может использовать strictfp.

И так же не требуется, но рекомендуется использовать такой порядок:
@Аннотации, доступ,
abstract static final synchronized native strictfp

Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥61
📎Паттерн Фасад (Facade)

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

Использование:

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

Преимущества:

1️⃣ Упрощает взаимодействие с комплексной системой, предоставляя удобный интерфейс.
2️⃣ Снижает зависимость клиента от конкретных классов подсистем, облегчая поддержку и тестирование.
3️⃣ Уменьшает количество точек взаимодействия с системой, что помогает избежать ошибок и повысить удобство работы.

Недостатки:

1️⃣ Может скрывать избыточную функциональность, ограничивая возможности системы.
2️⃣ Увеличивает количество кода за счёт добавления нового фасадного класса.
3️⃣ Если фасад слишком абстрактен, может стать сложным для понимания и поддержки.

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

Java библиотека #java
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥107👍2
Что такое короткое замыкание логического оператора?

Логические операторы || и && лево-ассоциативны, то есть их параметры вычисляются слева направо. Если первое значение оказалось true в || или false в && – конечный результат уже предрешен, он будет тем же. В этом случае происходит так называемое «короткое замыкание» (short-circuiting) – оставшийся второй аргумент не вычисляется за ненадобностью.

Эту особенность иногда удобно эксплуатировать, например для проверки на null в одну строку:
return param != null && param.getBoolMember();


Но иногда такая ситуация влечет за собой неожиданные плавающие баги, если второй аргумент – не переменная, а функция с побочным эффектом. Для этой ситуации введены версии операторов без короткого замыкания: | и &. Это логические вариации «битового и» и «битового или».

Вдобавок доступен оператор «исключающее или» ^. Он почти никогда не используется для булевых параметров, потому что абсолютно эквивалентен более интуитивно понятному !=. Другие битовые операторы для логических аргументов недоступны.


Java Guru🤓 #java
8👍4🔥1
Перечислите целочисленные битовые операторы

Во-первых, стоит освежить знания о бинарном представлении целых знаковых чисел. В Java используется подход two's complement – для значения 0 все биты нули, при переполнении максимального значения на 1 получается минимальное.

Бинарные битовые операторы &, | и ^ действуют очевидным образом: выполняют побитовые «И», «ИЛИ» и «исключающее ИЛИ» (XOR) соответственно. Здесь особенно интересен XOR:

Применение к значению «исключающего или» с одним и тем же параметром два раза дает то же значение. За счет этого его можно использовать как простейшее шифрование, аргумент выступит ключом;
С помощью XOR реализуется XOR-обмен – алгоритм обмена значениями переменных без дополнительной памяти и без риска переполнения. Это также один из популярных вопросов для собеседования.

Унарный оператор битового отрицания (дополнения) ~. Эквивалентен «исключающему или» с самим собой – все биты инвертируются. ~x эквивалентно -x-1. ~0 == -1.
Битовые сдвиги: левый << правый знаковый >> и правый беззнаковый >>>. Левый операнд – что сдвигать, правый – на сколько битов.

Второй параметр, дистанция сдвига, должен быть не больше доступных разрядов – 31 для int и 63 для long. Если передано значение больше – используются младшие 5 и 7 битов соответственно. То есть для int переменной x << 33 эквивалентно x << 2.

a << b эквивалентно умножению a на 2 в степени b.
a >> b совпадает с делением на 2 в степени b, с округлением вниз. Для положительных a то же что a/pow(2,b). Для не делящихся нацело на pow(2,b) отрицательных это a/pow(2,b)-1.

Беззнаковый сдвиг вправо трактует число не как two's complement, а как беззнаковое. То есть Integer.MIN_VALUE будет сдвинут так, как будто это сдвигается число на 1 большее чем Integer.MAX_VALUE.

Беззнакового сдвига влево не существует, потому что он совпадал бы со знаковым сдвигом, и был бы избыточным.


Java Guru🤓 #java
🔥64👍3
Как узнать, является ли A подтипом B?

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

instanceof – бинарный оператор, самый быстрый и самый используемый. Если есть экземпляр A и можно указать B явно, выбирать надо его. Если A (точнее тип хранящей экземпляр A переменной) и B не из одной цепочки наследования – экземпляр точно не может быть подтипом B и компиляция упадет с ошибкой inconvertible types.

Class::isInstance – метод принимает параметром объект типа A. Его стоит выбрать, когда экземпляр A в наличии, но B – неизвестный на этапе компиляции тип. То есть, для переменных A a и Class bClass, можем проверить bClass.isInstance(a).

Class::isAssignableFrom – принимает Class<A>. Единственное, что остается, если экземпляра A нет. bClass.isAssignableFrom(aClass).

Есть еще четвертый способ – имея экземпляр типа A привести его к B. Если типы были несовместимы, приведение выбросит ClassCastException. Это во всех смыслах плохой способ, построению логики программы на исключениях нет оправдания. Подробная аргументация описана в Effective Java Item 57.


Java Guru🤓 #java
6👍5🔥3
Чем анонимный внутренний класс отличается от лямбды?

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

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

Лямбдой реализуется только функциональный интерфейс. Функциональный интерфейс – это тип с единственным абстрактным методом. Анонимным классом же можно расширить любой расширяемый класс или реализовать интерфейс с любым количеством абстрактных методов.

Анонимный класс создает новый скоуп, лямбда работает в текущем. Это значит, что объявление переменной с именем, которое уже используется снаружи, в лямбде вызовет ошибку компиляции «variable is already defined», в анонимном классе скроет (shadowing) внешнюю переменную.

С точки зрения реализации JVM, для лямбды не создается дополнительного .class файла, как это происходит для анонимного класса. Соответственно, не происходит и обычной загрузки и верификации класса. Вместо этого используется механизм invokedynamic и класс генерируется на лету с помощью LambdaMetafactory. Так что лямбда-выражения обычно работают быстрее.


Java Guru🤓 #java
🔥124👍4🌭1
Как объявить переменное количество аргументов метода?

Для этого используется аргумент-массив. В нем может находиться любое количество элементов. Еще с Java 5 для этого случая добавился синтаксический сахар: Variable-length argument (vararg). Три точки ... ставятся между типом и именем переменной, и становится можно передать любое количество аргументов, не упаковывая их в массив.

На уровне байткода применение массива и варарга не отличаются: vararg-параметр Foo... превращается в параметр-массив Foo[], на этапе вызова подставляется неявное инстанцирование и заполнение массива.

Чтобы избежать неоднозначностей, на vararg наложено ограничение: им может быть только один последний аргумент.

Vararg, как массив, может быть пустым. Иногда это приводит к неочевидному поведению. Допустим, имеем две перегрузки метода с аргументами int... и float.... Вызов такого метода без параметров попадает в вариант с int, как с более специфичным типом. Наличие же перегрузки с несовместимыми типами, например int... и boolean..., приводит при вызове к ошибке компиляции «Ambiguous method call».

Когда типом варарга используется generic-параметр, возникает warning «Possible heap pollution from parameterized vararg type». Вам нужно убедиться, что вы понимаете в чем этот риск, что ваш код не приводит к heap pollution, и уведомить об этом компилятор аннотацией
@SafeVarargs.

Java Guru🤓 #java
👍103🔥2
Что будет при вызове /api/customers/{5}, если записи c id 5 нет
👍6🔥3
🔥7👍51
Что если блок finally выбросит исключение?

Блок finally выполняется вне зависимости от того, было ли выброшено в его блоке try исключение, или нет. Когда исключение не случилось в try, но случилось в finally, нет ничего интересного. Это исключение как обычно пойдет вверх по стеку вызовов, пока не попадется в соответствующий ему catch.

Вопрос подразумевает второй случай, когда исполнение попадает в finally в момент выброса исключения из try. Исключение из finally заменит собой исключение из try и пойдет вверх по стеку вместо него. Оригинальная проблема будет попросту забыта.

Такая маскировка исключения усложняет отладку, лучше избегать её. Например IntelliJ IDEA выводит соответствующее предупреждение на оператор throw внутри finally.


Java Guru🤓 #java
6👍6🔥2
Как скопировать массив?

Object.clone()
и System.arraycopy(). Нативные способы копирования, самые быстрые из возможных. Унаследованный от базового класса clone() копирует весь массив без лишних аргументов. arraycopy(), наоборот, максимально гибкий – позволяет копировать часть массива и указывать массив, в который копировать.

Arrays.copyOf(), Arrays.copyOfRange() и все их перегрузки. Утилитарные методы, которые дают более специализированные способы копирования. Внутри все используют System.arraycopy().

Копирование через стрим.
Arrays.stream(sourceArray).toArray(). Удобно когда нужно встроить дополнительные промежуточные операции.

Сторонними библиотеками. Обычно обеспечивают дополнительные удобства, такие как проверка корректности параметров, приведения типов, и прочие. Пример – класс SerializationUtils из Apache Commons.

Все перечисленные способы создают поверхностную копию – оба массива в итоге ссылаются на одни и те же объекты. Лучший способ создать глубокую копию – реализовать ее вручную. Сначала оператором new создается пустой массив нужного размера, затем в цикле заполняется копиями элементов. Stream API здесь может дать удобный интерфейс реализации, и оптимизировать копирование с помощью многопоточности.

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


Java Guru🤓 #java
👍93🔥3
Чем отличается Closeable от AutoCloseable?

Интерфейс AutoCloseable представляет объект-хранилище некоего ресурса, пока тот не закрыт. В единственном его методе close() объявляется логика закрытия этого ресурса. Пример – дескриптор открытого файла (ObjectOutputStream).

Особенность этого интерфейса в том, что его применение позволяет использовать объект в языковой конструкции try-with-resource. Всё это появилось в Java версии 7.

До Java 7 уже существовал похожий интерфейс – Closeable. Смысл его точно такой же. Он всё еще доступен в стандартной библиотеке для обратной совместимости, но в новом коде рекомендуется использовать AutoCloseable. Чтобы экземпляры старого Closeable тоже можно было использовать в try-with-resource, новый интерфейс был добавлен его родителем.

Проблема старого интерфейса Closeable была в узости типа исключений, которые может выбрасывать close(). Ковариантность позволила расширить тип в базовом интерфейсе AutoCloseable с IOException до Exception.

Еще одно отличие – контракт метода close(). Старый Closeable требует его идемпотентности, тогда как новый AutoCloseable разрешает методу иметь побочные эффекты.


Java Guru🤓 #java
👍13🔥5
Какая строка инициализации приведёт к ошибке?
👍3🔥2
Какая строка инициализации приведёт к ошибке?
Anonymous Quiz
40%
1
6%
2
5%
3
32%
4
17%
Ошибки не будет
🔥9👍5
Как Java выбирает перегруженный метод?

Метод может быть перегружен различными параметрами – в классе могут существовать несколько разных методов с одинаковым названием. При вызове такого метода выбор конкретного варианта происходит на этапе компиляции (раннее связывание). В деталях алгоритм выбора перегруженного метода описан в спецификации.

Выбор происходит в два шага. На первом выбирается одна из трех фаз – множество подходящих методов.

1. Методы, в которые переданные параметры подходят по типу либо как есть, либо с применением расширения (upcasting) примитивов или ссылочных типов, исключая vararg-параметры.

2. Если в фазе 1 подходящих методов не нашлось, к ее условиям добавляются возможность боксинга/анбоксинга параметров. Обратите внимание, в комбинации работает только боксинг+расширение, но не наоборот.

3. Если и для фазы 2 нет удовлетворительных сигнатур, к условиям поиска подключаются vararg-параметры.

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

Когда в фазе имеется несколько подходящих методов, используется наиболее специфичный среди них (но только в рамках данной фазы!).

Метод A считается более специфичным чем B, когда типы параметров одного метода – подтипы типов параметров другого. То есть любые возможные значения аргументов A подошли бы и для B, но не наоборот.

В условии специфичности говорится о типах параметров метода, а не о типах передаваемых значений. Так что боксинг/анбоксинг не учитывается, и метод с параметром int не считается более специфичным, чем с параметром Object (в отличие от Integer). Хотя, целое число можно передавать и как Object, и как Integer.
Подробное объяснение.

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


Java Guru🤓 #java
🔥11👍53
Что вернётся для запроса /api/hello?name=Spring
👍4🔥3
Что вернётся для запроса /api/hello?name=Spring
Anonymous Quiz
4%
Hello, name
15%
Hello, World
73%
Hello, Spring
5%
400 Bad Request
3%
404 Not Found
👍5🔥2
Зачем нужно ключевое слово assert?

assert – не то же самое, что методы вроде assertTrue() из тестовых библиотек. Это зарезервированное ключевое слово, унарный оператор.

Этот оператор ничего не возвращает, а принимает проверяемое утверждение типа boolean. Если значение оказывается false, проверка утверждения считается проваленной и выбрасывается AssertionError. Это похоже на сокращенную запись пары if и throw, с фиксированным типом исключения.

В Java до версии 4 слово assert не было ключевым. Поэтому для обратной совместимости механизм проверки утверждений выключен по умолчанию – логика программы никогда не должна полагаться на assert!

Включается флагом -ea или -enableassertions команды java. Можно указывать конкретные классы и пакеты в которых включить. Есть противоположный флаг -da (-disableassertions), эти флаги можно использовать в комбинации.

Assertion-ы используются в основном для дополнительной проверки инвариантов состояния объекта и для подстраховки в коде, который не должен никогда вызываться. Выброшенный AssertionError обычно означает ошибку программиста.

Дополнительно у оператора assert есть синтаксис передачи параметра detailMessage в конструктор AssertionError:
assert 2*2==5 : "two times two is not five!";

Java Guru🤓 #java
👍85🔥3