Защита вашего приложения с помощью Spring Security и OAuth2 Login.
Публичный доступ разрешён к
Полное руководство читайте здесь: тык
👉 Java Portal
Публичный доступ разрешён к
/
и /login
, все остальные эндпоинты требуют аутентификации.Полное руководство читайте здесь: тык
Please open Telegram to view this post
VIEW IN TELEGRAM
image_2025-06-22_08-35-57.png
2.3 MB
ReadWriteLock в Java
Этот механизм значительно повышает пропускную способность в приложениях с преобладанием операций чтения по сравнению с использованием обычного взаимного исключения (например,
Как работает
🔹 Read Lock (блокировка на чтение):
Несколько потоков могут одновременно получить блокировку на чтение, если ни один поток не владеет блокировкой на запись и ни один поток не ожидает записи. Это позволяет выполнять параллельные операции чтения, что эффективно для ресурсов, к которым часто обращаются на чтение и редко — на запись.
🔹 Write Lock (блокировка на запись):
Только один поток может получить блокировку на запись, и только если в данный момент нет активных читателей и писателей. Это обеспечивает исключительный доступ при модификации данных, предотвращая неконсистентность.
Java предоставляет стандартную реализацию интерфейса
Пример применения
Допустим, у вас есть общая структура данных, например, кэш или список, к которым обращаются несколько потоков:
🔹 Читатели: множество потоков часто читают данные.
🔹 Писатели: время от времени поток обновляет данные.
Используя
👉 Java Portal
ReadWriteLock
— это утилита для работы с параллелизмом в Java, которая позволяет нескольким потокам одновременно читать ресурс, но только одному потоку — записывать, и только в том случае, если никакие другие потоки в данный момент не читают и не пишут.Этот механизм значительно повышает пропускную способность в приложениях с преобладанием операций чтения по сравнению с использованием обычного взаимного исключения (например,
synchronized
или ReentrantLock
), где доступ к ресурсу может получить только один поток за раз.Как работает
ReadWriteLock
Несколько потоков могут одновременно получить блокировку на чтение, если ни один поток не владеет блокировкой на запись и ни один поток не ожидает записи. Это позволяет выполнять параллельные операции чтения, что эффективно для ресурсов, к которым часто обращаются на чтение и редко — на запись.
Только один поток может получить блокировку на запись, и только если в данный момент нет активных читателей и писателей. Это обеспечивает исключительный доступ при модификации данных, предотвращая неконсистентность.
Java предоставляет стандартную реализацию интерфейса
ReadWriteLock
в виде класса ReentrantReadWriteLock
.Пример применения
Допустим, у вас есть общая структура данных, например, кэш или список, к которым обращаются несколько потоков:
Используя
ReadWriteLock
, вы позволяете всем читателям одновременно обращаться к данным, но гарантируете, что при обновлении данных писатель будет иметь исключительный доступ. Это повышает производительность и обеспечивает потокобезопасность.Please open Telegram to view this post
VIEW IN TELEGRAM
Это шпаргалка по основным командам Linux, разбитая по категориям.
🔹 Файлы и навигация
🔹 Системная информация
🔹 Сетевые команды
🔹 Процессы
🔹 Архивация и сжатие
🔹 Права доступа
🔹 Другие команды
👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
Параллельная хеш-таблица с мелкозернистой блокировкой:
Потокобезопасная хеш-таблица с блокировками на уровне бакетов для минимизации конфликтов — подходит для высокопроизводительных систем, таких как базы данных или кеши.
👉 Java Portal
Потокобезопасная хеш-таблица с блокировками на уровне бакетов для минимизации конфликтов — подходит для высокопроизводительных систем, таких как базы данных или кеши.
Please open Telegram to view this post
VIEW IN TELEGRAM
Java Enums — Всё, что нужно знать
1.
• Это называется enum (сокращение от enumeration).
• Используется для задания фиксированного набора констант (например, состояния заказа, роли пользователя).
2.
• Бросает исключение, если строка некорректна.
• Используется при маппинге из БД, пользовательского ввода или JSON (например, парсинг статуса из запроса).
3.
• Возвращает имя enum-константы в точности как оно определено.
• Используется при логировании, сериализации и т. д. (например, отображение enum как строки в ответе).
4.
• Возвращает позицию (начиная с 0).
• Используется только если порядок важен (обычно избегается ради стабильности) — например, для ранжирования уровней.
5.
• Используется для итерации по всем константам.
• Удобно для UI-дропдаунов, фильтров, пакетной обработки.
6.
• Добавляет поведение к enum.
• Используется, если для каждой константы нужна логика (например, isHoliday() для Day).
7.
• Используется для хранения дополнительных данных с каждой константой (например, код из БД, описание).
8. Enum с абстрактным методом
• Называется поведенческим enum'ом.
• Используется для логики, подобной конечному автомату (например, стратегии на константу).
9.
• Высокопроизводительное множество для enum.
• Используйте вместо HashSet<Enum> (например, для фильтрации флагов или ролей).
10.
• Эффективная key-value структура, использующая enum в качестве ключа.
• Быстрее и легче, чем HashMap<Enum, X> (например, для меток или конфигураций по статусу).
11.
• Работает с enum'ами нативно.
• Используется для route-логики (например, обработки каждого статуса по-своему).
👉 Java Portal
1.
enum Status { PENDING, APPROVED, REJECTED }
→ Базовый Enum• Это называется enum (сокращение от enumeration).
• Используется для задания фиксированного набора констант (например, состояния заказа, роли пользователя).
2.
Status.valueOf("APPROVED")
→ Преобразование строки в Enum• Бросает исключение, если строка некорректна.
• Используется при маппинге из БД, пользовательского ввода или JSON (например, парсинг статуса из запроса).
3.
Status.APPROVED.name()
→ Получить имя как строку• Возвращает имя enum-константы в точности как оно определено.
• Используется при логировании, сериализации и т. д. (например, отображение enum как строки в ответе).
4.
Status.APPROVED.ordinal()
→ Получить индекс Enum• Возвращает позицию (начиная с 0).
• Используется только если порядок важен (обычно избегается ради стабильности) — например, для ранжирования уровней.
5.
for (Status s : Status.values())
→ Перебор значений Enum• Используется для итерации по всем константам.
• Удобно для UI-дропдаунов, фильтров, пакетной обработки.
6.
enum Day { MON, TUE; boolean isWeekend() { return false; } }
→ Enum с методами• Добавляет поведение к enum.
• Используется, если для каждой константы нужна логика (например, isHoliday() для Day).
7.
enum Type { BASIC("B"), PREMIUM("P"); private String code; ... }
→ Enum с полями• Используется для хранения дополнительных данных с каждой константой (например, код из БД, описание).
8. Enum с абстрактным методом
enum Mode {
ON { void act() { /* что-то делаем */ } },
OFF { void act() { /* ничего не делаем */ } };
abstract void act();
}
• Называется поведенческим enum'ом.
• Используется для логики, подобной конечному автомату (например, стратегии на константу).
9.
EnumSet.of(Status.APPROVED)
→ EnumSet• Высокопроизводительное множество для enum.
• Используйте вместо HashSet<Enum> (например, для фильтрации флагов или ролей).
10.
EnumMap<Status, String>
→ EnumMap• Эффективная key-value структура, использующая enum в качестве ключа.
• Быстрее и легче, чем HashMap<Enum, X> (например, для меток или конфигураций по статусу).
11.
switch(status)
→ Enum в операторе switch• Работает с enum'ами нативно.
• Используется для route-логики (например, обработки каждого статуса по-своему).
Please open Telegram to view this post
VIEW IN TELEGRAM
Что такое daemon-поток в Java?
Daemon-поток — это фоновый поток, который работает для поддержки пользовательских потоков. Примеры — сборщик мусора или системы мониторинга. Главное отличие: daemon-потоки не мешают завершению работы JVM. Как только все пользовательские (не-daemon) потоки завершатся, JVM завершится автоматически, даже если daemon-потоки ещё работают.
Как создаете daemon-поток?
🔹 Создаёте поток как обычно
🔹 Вызываете
🔹 Запускаете поток через
Как только основной (пользовательский) поток завершится — JVM выключится, даже если daemon-поток всё ещё активен.
Используйте daemon-потоки для фоновых сервисов, которые не должны блокировать завершение приложения. Всегда вызывайте
👉 Java Portal
Daemon-поток — это фоновый поток, который работает для поддержки пользовательских потоков. Примеры — сборщик мусора или системы мониторинга. Главное отличие: daemon-потоки не мешают завершению работы JVM. Как только все пользовательские (не-daemon) потоки завершатся, JVM завершится автоматически, даже если daemon-потоки ещё работают.
Как создаете daemon-поток?
setDaemon(true)
до запуска потокаstart()
Как только основной (пользовательский) поток завершится — JVM выключится, даже если daemon-поток всё ещё активен.
Используйте daemon-потоки для фоновых сервисов, которые не должны блокировать завершение приложения. Всегда вызывайте
setDaemon(true)
до запускаPlease open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Разные способы защиты API-эндпоинтов
→ API-ключи
Клиент отправляет ключ в заголовках или query-параметрах
👍 Просто реализуется
👎 Нет идентификации пользователя, легко утечь
→ OAuth2 (токены доступа)
Механизм авторизации через токены с областью доступа и сроком действия
👍 Тонкая настройка прав доступа, широко поддерживается
👎 Более сложная настройка
→ JWT (JSON Web Tokens)
Самодостаточный токен с наборами claims
👍 Без состояния (stateless), хорошо масштабируется
👎 Сложно отзывать, больше размер
→ Basic Auth
Отправка Base64(username:password) в заголовке
👍 Удобно для быстрого тестирования
👎 Никогда не используйте в продакшене
→ mTLS (взаимная TLS-аутентификация)
И клиент, и сервер проверяют сертификаты друг друга
👍 Высокий уровень доверия и шифрования
👎 Сложная инфраструктура и управление сертификатами
Для внутренних сервисов → mTLS + токены с коротким сроком жизни
Для публичных API → OAuth2 + scopes (области доступа)
Для тестирования/разработки → API-ключи или Basic Auth
👉 Java Portal
→ API-ключи
Клиент отправляет ключ в заголовках или query-параметрах
→ OAuth2 (токены доступа)
Механизм авторизации через токены с областью доступа и сроком действия
→ JWT (JSON Web Tokens)
Самодостаточный токен с наборами claims
→ Basic Auth
Отправка Base64(username:password) в заголовке
→ mTLS (взаимная TLS-аутентификация)
И клиент, и сервер проверяют сертификаты друг друга
Для внутренних сервисов → mTLS + токены с коротким сроком жизни
Для публичных API → OAuth2 + scopes (области доступа)
Для тестирования/разработки → API-ключи или Basic Auth
Please open Telegram to view this post
VIEW IN TELEGRAM
Ограничение частоты запросов в Spring Boot с помощью Bucket4j
Реализация rate limiting в REST API на Spring Boot с использованием Bucket4j для контроля частоты запросов на пользователя, что повышает масштабируемость API. Подходит для предотвращения злоупотреблений в продакшн-системах
• Импортируются классы из Bucket4j, Spring и Java SDK.
• Создаётся REST-контроллер с маппингом
• Для каждого пользователя создаётся бакет с лимитом 5 запросов в минуту. Если бакет позволяет — запрос проходит. Если нет — возвращается 429 статус.
• Заглушка для
👉 Java Portal
Реализация rate limiting в REST API на Spring Boot с использованием Bucket4j для контроля частоты запросов на пользователя, что повышает масштабируемость API. Подходит для предотвращения злоупотреблений в продакшн-системах
• Импортируются классы из Bucket4j, Spring и Java SDK.
• Создаётся REST-контроллер с маппингом
/api
• Для каждого пользователя создаётся бакет с лимитом 5 запросов в минуту. Если бакет позволяет — запрос проходит. Если нет — возвращается 429 статус.
• Заглушка для
HttpServletResponse
, нужна чтобы код можно было запускать без настоящего HTTP-сервера.Please open Telegram to view this post
VIEW IN TELEGRAM
Все ключевые слова Java в одном месте
Java содержит 53 зарезервированных ключевых слова, которые нельзя использовать как имена переменных, методов или классов — каждое из них выполняет определённую роль в языке.
Независимо от того, только ли ты начинаешь изучать Java или готовишься к собеседованию — знание этих ключевых слов обязательно
👉 Java Portal
Java содержит 53 зарезервированных ключевых слова, которые нельзя использовать как имена переменных, методов или классов — каждое из них выполняет определённую роль в языке.
Независимо от того, только ли ты начинаешь изучать Java или готовишься к собеседованию — знание этих ключевых слов обязательно
Please open Telegram to view this post
VIEW IN TELEGRAM
Прокачай производительность своего Java-приложения с помощью мощного кэширования.
Узнай, как интегрировать Spring Boot, Redis и Docker в этом практическом гайде.
Читать сейчас: тык😈
👉 Java Portal
Узнай, как интегрировать Spring Boot, Redis и Docker в этом практическом гайде.
Читать сейчас: тык
Please open Telegram to view this post
VIEW IN TELEGRAM
Sealed-классы и интерфейсы в Java
Они позволяют явно контролировать, какие классы могут расширять или реализовывать тип — делая иерархии безопаснее, легче в сопровождении и идеально подходящими для исчерпывающего pattern matching'а
Начиная с Java 17, были введены sealed-классы и sealed-интерфейсы как способ ограничить и контролировать иерархию наследования.
Как это работает?
🔹 Используй модификатор
🔹 Все разрешённые подтипы должны находиться в одном модуле или пакете и обязаны быть помечены как
Зачем использовать sealed-классы и интерфейсы?
👉 Более строгая инкапсуляция и контроль над публичным API
👉 Безопасный и предсказуемый код (отлично подходит для pattern matching в
👉 Защита от нежелательного наследования или реализации
👉 Java Portal
Они позволяют явно контролировать, какие классы могут расширять или реализовывать тип — делая иерархии безопаснее, легче в сопровождении и идеально подходящими для исчерпывающего pattern matching'а
Начиная с Java 17, были введены sealed-классы и sealed-интерфейсы как способ ограничить и контролировать иерархию наследования.
Как это работает?
sealed
и ключевое слово permits, чтобы явно указать разрешённые подклассы или реализации.final
, sealed или non-sealed
Зачем использовать sealed-классы и интерфейсы?
switch
)Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Давайте разберёмся с Git Merge и Rebase
Git предоставляет два мощных инструмента для интеграции изменений из одной ветки (часто feature-ветки) в другую (обычно main):
Merge и Rebase
Оба инструмента решают одну задачу — объединение кода, но делают это по-разному.
✔️ Git Merge
Merge создаёт новый коммит, который фиксирует объединение двух веток.
В истории это видно как слияние двух независимых линий разработки в одной точке.
Шаги:
1. Переключись на целевую ветку (обычно main)
2. Выполни
Результаты:
🔸 Создаётся новый merge-коммит в целевой ветке с ссылками на родительские коммиты
🔸 История целевой ветки становится разветвлённой, отражая все слияния
🔸 Это более безопасный вариант для командной работы — история изменений сохраняется
✔️ Git Rebase
Rebase переписывает историю ветки (обычно feature-ветки), как будто коммиты были сделаны поверх другой ветки (например, main).
Получается более линейная история без merge-коммитов.
Шаги:
1. Переключись на ветку, которую хочешь ребейзнуть (например, feature)
2. Выполни
Результаты:
🔸 Коммиты из feature будут повторно применены на вершину main, создаются новые коммиты с теми же изменениями
🔸 Целевая ветка (main) при этом не изменяется
🔸 История feature-ветки становится линейной, но её история будет переписана
Примечания:
После успешного rebase, финальное merge в main часто выполняется как fast-forward, т.к. история выглядит непрерывной
Rebase изменяет ветку, на которой ты находишься, а не целевую ветку
Итог:
👉 Java Portal
Git предоставляет два мощных инструмента для интеграции изменений из одной ветки (часто feature-ветки) в другую (обычно main):
Merge и Rebase
Оба инструмента решают одну задачу — объединение кода, но делают это по-разному.
Merge создаёт новый коммит, который фиксирует объединение двух веток.
В истории это видно как слияние двух независимых линий разработки в одной точке.
Шаги:
1. Переключись на целевую ветку (обычно main)
2. Выполни
git merge <feature>
— укажи имя исходной веткиРезультаты:
Rebase переписывает историю ветки (обычно feature-ветки), как будто коммиты были сделаны поверх другой ветки (например, main).
Получается более линейная история без merge-коммитов.
Шаги:
1. Переключись на ветку, которую хочешь ребейзнуть (например, feature)
2. Выполни
git rebase <main>
Результаты:
Примечания:
После успешного rebase, финальное merge в main часто выполняется как fast-forward, т.к. история выглядит непрерывной
Rebase изменяет ветку, на которой ты находишься, а не целевую ветку
Итог:
merge
сохраняет историю как есть → хорошо для командной работыrebase
делает историю аккуратной → хорошо для чистоты, но требует осторожностиPlease open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Базовые приёмы экономии памяти в Java
1. Ленивая инициализация "тяжёлых" полей
– Используйте lazy initialization, чтобы откладывать создание объекта до момента, когда он действительно понадобится.
2. Избегайте анонимных внутренних классов в циклах
– Каждое такое использование создаёт объект внутреннего класса — это скрытая нагрузка на память.
3. Используйте статические фабричные методы
– Они позволяют экономить память за счёт повторного использования уже созданных экземпляров, вместо создания новых при каждом вызове.
4. Применяйте метод intern()
– Метод
Используя
5. Используйте паттерн Flyweight для повторяющихся неизменяемых данных
– Экономит память за счёт разделения общих экземпляров (используется, например, в
👉 Java Portal
1. Ленивая инициализация "тяжёлых" полей
– Используйте lazy initialization, чтобы откладывать создание объекта до момента, когда он действительно понадобится.
2. Избегайте анонимных внутренних классов в циклах
– Каждое такое использование создаёт объект внутреннего класса — это скрытая нагрузка на память.
3. Используйте статические фабричные методы
– Они позволяют экономить память за счёт повторного использования уже созданных экземпляров, вместо создания новых при каждом вызове.
4. Применяйте метод intern()
– Метод
intern()
класса String возвращает каноническое представление строки.Используя
intern()
, можно гарантировать, что одна и та же строка будет представлена в памяти в виде единственного объекта, даже если она создаётся многократно.String s1 = "hello";
String s2 = "hello".intern();
5. Используйте паттерн Flyweight для повторяющихся неизменяемых данных
– Экономит память за счёт разделения общих экземпляров (используется, например, в
Integer.valueOf()
, Boolean.TRUE
и т.п.).Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Проектирование Backend-системы. Система уведомлений
Цель —> Разработать систему для доставки уведомлений пользователям через:
➣ Встроенные уведомления
➣ Push-уведомления (мобильные/веб)
➣ Email
С поддержкой:
✓Масштабируемости (миллионы пользователей)
✓Настраиваемых пользовательских предпочтений
✓Надежности, повторных попыток и дедупликации
✓Реального времени и пакетных оповещений
Пошаговый сквозной процесс
Шаг 1: Срабатывание события
✓ Приложение инициирует событие:
➣ Например, пользователь A лайкнул ваш пост
➣ Отправляется запрос POST на API /notify:
Шаг 2: Добавление в очередь уведомлений
➣ API валидирует данные и помещает сообщение в очередь уведомлений (Kafka/RabbitMQ)
Шаг 3: Обработка воркером
✓ Воркер извлекает сообщение из очереди:
➣ Получает полную информацию о событии
➣ Обращается к сервису предпочтений:
- Подписан ли UserB на уведомления типа "like"?
- Хочет email или только in-app?
✓ Создает payload уведомления, например:
✓
Шаг 4: Рассылка по каналам
✓ На основе предпочтений воркер запускает соответствующие каналы:
➣ In-App Writer → сохраняет в БД (для отображения иконки колокольчика)
➣ Push-сервис → отправляет push (мобильный/веб) через FCM/APNs
➣ Email-сервис → ставит email в очередь (через SES/SendGrid)
Шаг 5: Отправка и повторные попытки
✓ Сервисы каналов:
➣ Декуплированы для изоляции отказов
➣ Повторяют попытки при сбоях (3–5 раз с экспоненциальной задержкой)
➣ Логируют статус (отправлено, ошибка, пропущено)
Шаг 6: Отображение in-app уведомлений
✓ UI вызывает /notifications?user=UserB
✓ Возвращаются N последних уведомлений из БД:
Функции надежности
✓ Гарантированная доставка хотя бы один раз с идемпотентной записью
✓ Повторные попытки с экспоненциальной задержкой
✓ DLQ (Dead Letter Queue) для сообщений с ошибками
✓ Метрики: успех/неудача на сообщение
✓ Алерты при переполнении очереди или всплесках ошибок
👉 Java Portal
Цель —> Разработать систему для доставки уведомлений пользователям через:
➣ Встроенные уведомления
➣ Push-уведомления (мобильные/веб)
С поддержкой:
✓Масштабируемости (миллионы пользователей)
✓Настраиваемых пользовательских предпочтений
✓Надежности, повторных попыток и дедупликации
✓Реального времени и пакетных оповещений
Пошаговый сквозной процесс
Шаг 1: Срабатывание события
✓ Приложение инициирует событие:
➣ Например, пользователь A лайкнул ваш пост
➣ Отправляется запрос POST на API /notify:
{
"event_type": "like",
"actor": "UserA",
"receiver": "UserB",
"object": "Post123"
}
Шаг 2: Добавление в очередь уведомлений
➣ API валидирует данные и помещает сообщение в очередь уведомлений (Kafka/RabbitMQ)
Шаг 3: Обработка воркером
✓ Воркер извлекает сообщение из очереди:
➣ Получает полную информацию о событии
➣ Обращается к сервису предпочтений:
- Подписан ли UserB на уведомления типа "like"?
- Хочет email или только in-app?
✓ Создает payload уведомления, например:
✓
"UserA лайкнул ваш пост 'How to scale systems'"
Шаг 4: Рассылка по каналам
✓ На основе предпочтений воркер запускает соответствующие каналы:
➣ In-App Writer → сохраняет в БД (для отображения иконки колокольчика)
➣ Push-сервис → отправляет push (мобильный/веб) через FCM/APNs
➣ Email-сервис → ставит email в очередь (через SES/SendGrid)
Шаг 5: Отправка и повторные попытки
✓ Сервисы каналов:
➣ Декуплированы для изоляции отказов
➣ Повторяют попытки при сбоях (3–5 раз с экспоненциальной задержкой)
➣ Логируют статус (отправлено, ошибка, пропущено)
Шаг 6: Отображение in-app уведомлений
✓ UI вызывает /notifications?user=UserB
✓ Возвращаются N последних уведомлений из БД:
[
{ "text": "UserA лайкнул ваш пост", "read": false, ... }
]
Функции надежности
✓ Гарантированная доставка хотя бы один раз с идемпотентной записью
✓ Повторные попытки с экспоненциальной задержкой
✓ DLQ (Dead Letter Queue) для сообщений с ошибками
✓ Метрики: успех/неудача на сообщение
✓ Алерты при переполнении очереди или всплесках ошибок
Please open Telegram to view this post
VIEW IN TELEGRAM
Хаки Java-кодинга, которые стоит знать разработчикам
✓ Используйте
➣ Почему: короче, безопаснее, неизменяемо — снижает риск случайной мутации.
✓ Используйте
➣ Почему: устраняет проверки на
✓ Используйте
➣ Почему: чище и удобнее для сериализации/десериализации enum'ов в API, БД или файлах.
✓ Используйте
➣ Почему: читаемо, понятно, без ошибок в условных сравнениях.
✓ Используйте
➣ Почему: выразительнее и чище — больше никаких ручных
👉 Java Portal
✓ Используйте
Map.ofEntries()
для создания неизменяемых мап с несколькими парами➣ Почему: короче, безопаснее, неизменяемо — снижает риск случайной мутации.
✓ Используйте
Optional.map().orElse()
вместо ручной обработки null
➣ Почему: устраняет проверки на
null
, читается лучше, легко комбинируется с другой логикой.✓ Используйте
Enum.valueOf()
+ name()
для безопасного преобразования enum
➣ Почему: чище и удобнее для сериализации/десериализации enum'ов в API, БД или файлах.
✓ Используйте
Comparator.comparing().thenComparing()
вместо кастомной логики➣ Почему: читаемо, понятно, без ошибок в условных сравнениях.
✓ Используйте
Optional.ifPresentOrElse()
вместо if-else
с isPresent()
➣ Почему: выразительнее и чище — больше никаких ручных
get()
и нагромождений if-else
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM