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

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

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

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Чем отличается перегрузка от переопределения?

Полиморфизм – соль ООП. Перегрузка (overload) и переопределение (override) – два инструмента достижения полиморфного поведения в Java.

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

Ad-hoc – не совсем настоящий полиморфизм, так как при нём используется раннее, или статическое связывание (early binding, static dispatch). Это значит, что для выбора конкретного варианта метода используется информация о типе переменной, а не объекта в ней лежащего, и происходит это еще при компиляции.

Если в классе объявлены два перегруженных метода, а аргумент в вызове подходит под оба, случится ошибка компиляции. В примере выше компилятор не может выбрать между вариантами метода println с параметром char[] и со String, так как null может быть и тем и другим.

Переопределение (override) дает полиморфизм подтипов. Это реализация/подмена метода нефинального родительского класса или интерфейса. С помощью этого механизма достигается поведение, когда экземпляр хранится под типом родителя, но реализация методов используется специфичная для этого конкретного подтипа. Пример:
List<String> list = new LinkedList<>();
list.add(“foo“);


Здесь метод add вызывается общий для всех списков, но добавлен будет именно элемент связного списка.

Выбор конкретного метода происходит в последний момент, в процессе работы программы, в зависимости от типа объекта. Это называется позднее или динамическое связывание методов (late binding, dynamic dispatch).

Переопределение имеет непосредственное отношение к принципу подстановки Лисков (LSP): в хорошем объектно-ориентированном коде для вызывающего кода переопределенный метод не должен быть отличим от оригинального.

Переопределенный метод принято снабжать аннотацией
@Override. Ее отсутствие допускается, но компиляция не перегружающего метода с такой аннотацией приведет к ошибке.

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

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


Java Guru🤓 #java
👍146🔥4
Какое регулярное выражение соответствует строке, содержащей только цифры и имеющей длину от 3 до 5 символов?
Anonymous Quiz
50%
^[0-9]{3,5}$
15%
^[\d][3,5]$
16%
\d(3,5}
10%
*[0-9]{3.5}
9%
\d{3,5}/
🔥11👍54
Когда Class.getClassLoader вернет null?

Этот вопрос поднимает две темы. Первая – класс Class в целом. Экземпляры Class<T> представляют runtime-описание типов. В терминах этого описания перечисления считаются классами, аннотации – интерфейсами. В основном приходится взаимодействовать с метаклассами при работе с рефлексией или загрузчиками.

По большей части эти экземпляры класса Class состоят из содержимого .class-файла. Создаются они только внутри класслоадера. Особенности их хранения в памяти обсуждаются в
предыдущем посте.

Вторая тема для разговора здесь – особенности класса Class для примитивов, массивов и void. Для получения таких экземпляров используется тот же синтаксис, что и для обычных классов: void.class, int.class, float[][].class. Конструкция foo.class – это не обращение к члену, а литерал класса.

Для void типом-параметром T выступает специальный неинстанциируемый тип java.lang.Void. Тип-параметр примитива – соответствующий класс-враппер. Хотя для самого класса-враппера будет отдельный экземпляр Class. То есть int.class != Integer.class.

Метод getClassLoader обычного класса или интерфейса вернет загрузчик, который его загрузил. null может вернуться для загруженного bootstrap-класслоадером типа. Для массива возвращается то же, что для типа его элементов. Для примитивов и void результатом всегда будет null.


Java Guru🤓 #java
🔥72
Как узнать, является ли 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
🔥8👍42
Что вернётся, если отправить GET-запрос на /test?
👍5🔥2
🔥10👍6
Что такое Reflection и как его использовать?

Reflection, рефлексия – это средства манипуляции данными на основе знания о структуре классов этих данных, инструменты метапрограммирования.

Класс Class<T> используется как точка входа в мир рефлекшена. Его экземпляры предоставляют саму метаинформацию о содержимом класса и основные методы для работы с ним. Все классы относящиеся Java Reflection находятся в пакетах java.lang и java.lang.reflect.

Экземпляр класса Class можно получить тремя способами:
🔘Литералом .class;
🔘Статическим фабричным методом Class.forName();
🔘Методом getClass() экземпляров класса.

Использование Reflection API медленное и небезопасное. Оно позволяет ломать инвариантность состояний экземпляра, нарушать инкапсуляцию, и даже менять финальные поля.

Использовать рефлексию естественно в тестовом коде, в инструментах разработки, в фреймворках (особенно в связке с runtime-аннотациями). Рефлекшн в ординарном бизнес-коде обычно говорит о больших проблемах проектирования.

Нередко на интервью просят продемонстрировать пример использования рефлекшна. Один из самых близких для backend-разработчика примеров – инициализация классов-конфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности таких классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как объявления элементов экосистемы фреймворка.


Java Guru🤓 #java
Please open Telegram to view this post
VIEW IN TELEGRAM
👍115🔥5
В чём отличия интерфейса от абстрактного класса?

Главное отличие – это семантика. Интерфейсы появились еще до Java, как важная концепция ООП. Смысл интерфейса – некое поведение, описание свойства. Причем если придерживаться принципа сегрегации интерфейсов, это описание единственного аспекта поведения.

Класс, даже абстрактный – это комбинация всех свойств и их реализаций, которыми определяются сущности некоторой категории (собственно, класса).

Отсюда вытекает естественность и необходимость множественного наследования для интерфейсов. Опыт таких языков как C++ показал, что множественное наследование классов не нужно и проблемно (см. проблема ромбовидного наследования). По факту же обычно нужно всего лишь переиспользование кода, что не относится к ООП и реализуется в некоторых языках «интерфейсами с независимым состоянием» – примесями.
В Java интерфейс в отличие от абстрактного класса не может иметь состояния. Реализация поведения же допустима только в двух случаях: для статических методов, и default для обычных. Статические методы являются частью всего класса, а не экземпляров. Дефолтная реализация, как говорилось ранее, добавлена только как хак для сохранения совместимости.

В интерфейсах, как публичных описаниях, не имеют смысла и запрещены непубличные члены. Отсюда синтаксическое отличие: модификатор public, как и abstract для методов или static для полей, можно не писать. Запрещены и модификаторы, несовместимые с abstract: final, synchronized и прочие.

На уровне скомпилированного байткода тоже есть небольшие различия: интерфейс помечается флагом ACC_INTERFACE а для класса генерируется конструктор по-умолчанию.

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


Java Guru🤓 #java
👍16🔥73
Что будет результатом запуска кода?
👍5🔥2
👍6🔥2
Что если оба реализуемых интерфейса объявляют один и тот же метод?

Если объявление полностью одинаково
– нет никакой проблемы, класс-реализация должен просто определить этот метод.

Когда у обоих интерфейсов объявлены методы с одинаковой сигнатурой, но разными возвращаемыми типами – всё зависит от того, какие именно эти типы.

Переопределение метода (override) еще с Java 5 ковариантно относительно возвращаемого типа. То есть, в наследнике тип результата метода может быть наследником: super метод возвращает Number,
@Override метод возвращает Integer.

Если типы не связаны отношением наследования, например String и Long – такой класс невозможно реализовать.

Для примитивов никакой ковариантности возвращаемого типа нет. Даже если типы совместимы относительно присваивания: int→long, int→Integer. В любом из таких случаев будет ошибка о несовместимости возвращаемых типов, для примитивов они должны совпадать в точности.

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


Java Guru🤓 #java
👍10🔥32
Как изменить значение приватного финального поля?

Стоит сразу сказать, это очень плохая практика. Такое изменение грубо нарушает принципы сокрытия данных, и потенциально ломает инвариантность состояния объекта.

Для этого трюка необходимо прибегнуть к использованию Reflection API.

Сначала получим дескриптор поля – экземпляр класса Field. У объекта метакласса Class<X> интересующего нас класса вызовем метод getDeclaredField(). Просто getField() не сработает, потому что он работает только с публичными полями. Параметром передается строка с именем поля.

Полученного экземпляра Field уже достаточно для доступа к изменяемым приватным полям. Перед обращением требуется сделать его доступным, вызвав setAccessible(true).

Сам доступ осуществляется методами get*() и set*(). Так как Field представляет дескриптор поля класса, без привязки к конкретному экземпляру класса, экземпляр передается параметром в методы доступа. Для статического поля передается null.

Чтобы побороть неизменяемость финального поля, нужно снять его модификатор final. Все модификаторы поля хранятся в поле modifiers дескриптора. То есть, нужно также с помощью рефлекшена сделать доступным и обновить поле уже объекта Field.

Поле modifiers хранит модификаторы в виде битовой маски. Для изменения придется прибегнуть к битовым операторам.

Полный код установки значения 42 в поле myField объекта myObject выглядит так:

Field field = myObject.class.getDeclaredField( "myField" );
field.setAccessible( true );
Field modifiersField = Field.class.getDeclaredField( "modifiers" );
modifiersField.setAccessible( true );
modifiersField.setInt( field, field.getModifiers() & ~Modifier.FINAL );
field.setInt(myObject, 42);


Java Guru🤓 #java
👍9🔥42
Приведите примеры операций со стримами

Самые часто используемые

Java Guru🤓 #java
🔥16👍85
При вызове метода executeTask() три раза, сколько экземпляров TaskProcessor будет создано?
👍5🔥5
При вызове метода executeTask() три раза, сколько экземпляров TaskProcessor будет создано?
Anonymous Quiz
51%
Один, так как TaskManager синглтон
2%
Два, просто на удачу
41%
Три, так как TaskProcessor prototype
3%
Ошибка компиляции
2%
RuntimeException
👍12🔥51🥴1
Что произойдёт при запуске Spring-приложения с таким классом?
👍4🔥3
Что произойдёт при запуске Spring-приложения с таким классом?
Anonymous Quiz
22%
Не запустится, так как нельзя использовать обе эти аннотации на одном классе
9%
Запустится, но Spring не создаст ни одного бина из-за конфликта
55%
Запустится, будет зарегистрирован один бин
6%
Запустится, но будет RuntimeException
👍17🔥5
Что будет результатом кода?
🔥5👍3
Что будет результатом кода?
Anonymous Quiz
33%
Ошибка компиляции
6%
RuntimeException
46%
0
13%
null
2%
number
🔥8👍7😁3
Как реализовать собственный стрим?

Любой стрим определяется его сплитератором. Spliterator – это специальный разделяемый внутренний итератор.

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

Поток создается из сплитератора одним из статических методов класса StreamSupport. Вызов его методов осуществляется самим фреймворком. Вкратце его работа выглядит так:
• Элементы перебираются методом tryAdvance, пока он не выдаст false. Через параметр action к элементу применяются последующие операции.
• При применении промежуточных и терминальных операций учитываются характеристики потока, изначально задаваемые методом characteristics.
• Когда обработка стрима распараллеливается, методом trySplit от начала последовательности элементов «откусывается» часть, и возвращается завернутой в новый сплитератор. Текущий продолжает идти по оставшемуся хвосту. В идеале, по возможности эта часть – половина элементов потока. Если разделить уже нельзя, возвращается null.


Java Guru🤓 #java
👍7🔥53