Приглашаем на открытый урок.
🗓 21 сентября в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java разработчик. Продвинутый уровень».
Разберем, как браузер общается с сервером, и создадим работающий HTTP-сервис без Spring и сторонних библиотек.
О чем поговорим:
🔗 Ссылка на регистрацию: https://vk.cc/d1sLwn
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🧠 Трюк с
Когда тебе нужно обрабатывать события в рамках транзакции, мы часто пишем:
⚠️ Но есть нюанс:
📌 Альтернатива: обычный
💡 Сниппет:
📎 Плюсы:
— Лучше контроль: ты сам решаешь, когда обрабатывать (до/после/вне транзакции)
— Можно централизовать поведение через utility-метод
— Гибкость: логика обработки не зависит от аннотаций Spring'а
⚠️ Минус: чуть больше кода, но понятнее поведение.
📲 Мы в MAX
👉@BookJava
@EventListener в Spring — убираем лишний @TransactionalEventListenerКогда тебе нужно обрабатывать события в рамках транзакции, мы часто пишем:
@TransactionalEventListener
public void handleEvent(MyEvent event) {
// ...
}
⚠️ Но есть нюанс:
@TransactionalEventListener по умолчанию срабатывает после коммита. Иногда это не очевидно и вызывает баги, особенно если ожидаешь, что событие обработается внутри транзакции.📌 Альтернатива: обычный
@EventListener, но вместе с TransactionSynchronizationManager.💡 Сниппет:
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// обработка события после коммита
}
}
);
} else {
// fallback: нет активной транзакции — выполняем сразу
}
}
}
📎 Плюсы:
— Лучше контроль: ты сам решаешь, когда обрабатывать (до/после/вне транзакции)
— Можно централизовать поведение через utility-метод
— Гибкость: логика обработки не зависит от аннотаций Spring'а
⚠️ Минус: чуть больше кода, но понятнее поведение.
📲 Мы в MAX
👉@BookJava
👍3🔥2
🧠
Оба способа хороши для конфигурации, но используют их по-разному. И если ты всё ещё везде пихаешь
📌
✅ Хорошо для единичных значений
❌ Плохо для сложных структур, списков, валидации
❌ Трудно покрыть тестами (без
❌ Нет биндинга по префиксу → нет группировки
📌
💡 Используй с
✅ Удобно группировать и документировать
✅ Работает с вложенными структурами, коллекциями
✅ Поддерживает JSR-303 валидацию (
✅ Легче мокать в тестах
✅ Интеграция с Spring Boot Actuator (
⚠️ Не смешивай: не нужно тянуть
Если конфигурация простая —
📲 Мы в MAX
👉@BookJava
@Value vs @ConfigurationProperties — не выбирай наобумОба способа хороши для конфигурации, но используют их по-разному. И если ты всё ещё везде пихаешь
@Value, держи краткий гайд, когда лучше что:📌
@Value — просто, но не гибко:
@Value("${my.prop}")
private String value;
✅ Хорошо для единичных значений
❌ Плохо для сложных структур, списков, валидации
❌ Трудно покрыть тестами (без
TestPropertySource)❌ Нет биндинга по префиксу → нет группировки
📌
@ConfigurationProperties — сила и масштаб:
@ConfigurationProperties(prefix = "app.feature")
public class FeatureProperties {
private boolean enabled;
private List<String> items;
}
💡 Используй с
@EnableConfigurationProperties или аннотируй как @Component✅ Удобно группировать и документировать
✅ Работает с вложенными структурами, коллекциями
✅ Поддерживает JSR-303 валидацию (
@Validated)✅ Легче мокать в тестах
✅ Интеграция с Spring Boot Actuator (
/actuator/configprops)⚠️ Не смешивай: не нужно тянуть
@Value внутрь @ConfigurationProperties — это антипаттерн.Если конфигурация простая —
@Value норм. Но как только появляется структура, коллекции, логика — всегда используй @ConfigurationProperties.📲 Мы в MAX
👉@BookJava
👍5🔥2
Double-brace инициализация в Java — это идиома, которая используется для инициализации коллекций (и иногда других объектов) в краткой форме. Она выглядит как две открывающие фигурные скобки подряд
Как это работает:
Double-brace инициализация — это комбинация двух конструкций:
1. Анонимный внутренний класс:
Создаётся новый безымянный подкласс
2. Инициализатор экземпляра:
Это блок, который выполняется при создании объекта. В него можно вставлять вызовы методов (например,
Преимущества:
* Компактный и удобочитаемый синтаксис для заполнения коллекций.
* Можно использовать в полях
Недостатки:
1. Создаётся лишний анонимный класс — это увеличивает количество байткода и может мешать сериализации.
2. Утечки памяти — если такой класс находится внутри внешнего класса, он может неявно хранить ссылку на него.
3. Читаемость — не все разработчики знают, как это работает, и это может сбивать с толку.
4. Нарушение принципов OOP — логика инициализации размещается в конструкторе, который не явно виден.
Альтернативы:
Java 8+ (через
Статический метод инициализации:
Java 9+ (immutable):
Вывод:
Double-brace инициализация — это удобный, но потенциально опасный трюк, который не рекомендуется использовать в продакшене. Лучше предпочесть более читаемые и безопасные альтернативы, особенно с учётом новых возможностей Java 8+.
📲 Мы в MAX
👉@BookJava
{{ и имеет специфическое поведение. Пример:
import java.util.*;
List<String> list = new ArrayList<String>() {{
add("one");
add("two");
add("three");
}};
Как это работает:
Double-brace инициализация — это комбинация двух конструкций:
1. Анонимный внутренний класс:
new ArrayList<String>() { ... }
Создаётся новый безымянный подкласс
ArrayList.2. Инициализатор экземпляра:
{{ ... }}
Это блок, который выполняется при создании объекта. В него можно вставлять вызовы методов (например,
add()).Преимущества:
* Компактный и удобочитаемый синтаксис для заполнения коллекций.
* Можно использовать в полях
final, например:
private static final Set<String> set = new HashSet<>() {{
add("A");
add("B");
}};
Недостатки:
1. Создаётся лишний анонимный класс — это увеличивает количество байткода и может мешать сериализации.
2. Утечки памяти — если такой класс находится внутри внешнего класса, он может неявно хранить ссылку на него.
3. Читаемость — не все разработчики знают, как это работает, и это может сбивать с толку.
4. Нарушение принципов OOP — логика инициализации размещается в конструкторе, который не явно виден.
Альтернативы:
Java 8+ (через
Stream и Collectors):
List<String> list = Stream.of("one", "two", "three")
.collect(Collectors.toList());
Статический метод инициализации:
public static List<String> createList() {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
return list;
}
Java 9+ (immutable):
List<String> list = List.of("one", "two", "three");
Set<String> set = Set.of("A", "B");
Вывод:
Double-brace инициализация — это удобный, но потенциально опасный трюк, который не рекомендуется использовать в продакшене. Лучше предпочесть более читаемые и безопасные альтернативы, особенно с учётом новых возможностей Java 8+.
📲 Мы в MAX
👉@BookJava
👍3❤1
📌 picocli — это современная библиотека для создания CLI-приложений на Java. Она упрощает разработку командных интерфейсов, обеспечивая:
* Автоматическую генерацию
* Поддержку подкоманд (как в
* Аргументы, параметры, опции с короткими и длинными флагами (
* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
📲 Мы в MAX
👉@BookJava
* Автоматическую генерацию
--help и --version* Поддержку подкоманд (как в
git commit, git push)* Аргументы, параметры, опции с короткими и длинными флагами (
-v, --verbose)* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
📲 Мы в MAX
👉@BookJava
👍5❤1🔥1
Что такое механизм try-with-resources?
Механизм
📌 Поддерживается с Java 7
📌 Ресурсы должны реализовывать интерфейс
💡 Пример:
🧠 Почему это важно:
* Уменьшает boilerplate-код
* Исключает утечки ресурсов
* Упрощает обработку исключений
⚠️ Совет:
С Java 9 можно использовать уже объявленные переменные, если они final или effectively final:
📲 Мы в MAX
👉@BookJava
Механизм
try-with-resources в Java — это конструкция, которая упрощает работу с ресурсами, требующими закрытия (например, файлы, сокеты, соединения с БД и т.д.). Он автоматически закрывает ресурсы после завершения блока try, избавляя от необходимости писать finally вручную.📌 Поддерживается с Java 7
📌 Ресурсы должны реализовывать интерфейс
AutoCloseable💡 Пример:
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// reader будет закрыт автоматически, даже если произойдёт исключение
🧠 Почему это важно:
* Уменьшает boilerplate-код
* Исключает утечки ресурсов
* Упрощает обработку исключений
⚠️ Совет:
С Java 9 можно использовать уже объявленные переменные, если они final или effectively final:
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
System.out.println(reader.readLine());
}
📲 Мы в MAX
👉@BookJava
👍4
🧠 Ленивая инициализация через
Когда нужно отложить создание тяжёлого объекта до первого обращения, многие вспоминают double-checked locking:
⚠️ Многословно, хрупко, легко ошибиться. Есть лучше.
📌 Современный подход — использовать
💡
Плюсы:
— Читается за секунду
— Потокобезопасно
— Нет дублирования кода
— Легко тестировать и заменять
🔁 Альтернатива в чистой Java: использовать
Если используешь Spring — можно просто обернуть бин в
📲 Мы в MAX
👉@BookJava
Supplier — элегантная альтернатива double-checked lockingКогда нужно отложить создание тяжёлого объекта до первого обращения, многие вспоминают double-checked locking:
private volatile SomeHeavyObject obj;
public SomeHeavyObject getObj() {
if (obj == null) {
synchronized (this) {
if (obj == null) {
obj = new SomeHeavyObject();
}
}
}
return obj;
}
⚠️ Многословно, хрупко, легко ошибиться. Есть лучше.
📌 Современный подход — использовать
Supplier с ленивой инициализацией:
private final Supplier<SomeHeavyObject> lazyObj = Suppliers.memoize(SomeHeavyObject::new);
public SomeHeavyObject getObj() {
return lazyObj.get();
}
💡
Suppliers.memoize — из Guava. Он гарантирует потокобезопасную инициализацию один раз при первом вызове get().Плюсы:
— Читается за секунду
— Потокобезопасно
— Нет дублирования кода
— Легко тестировать и заменять
🔁 Альтернатива в чистой Java: использовать
AtomicReference и updateAndGet, но это уже длиннее и менее выразительно.Если используешь Spring — можно просто обернуть бин в
@Lazy. Но вне Spring, в обычных Java-приложениях или утилитах — Supplier с memoize() идеален.📲 Мы в MAX
👉@BookJava
👍2🔥2
Три похожих слова в Java —
🔒
Ключевое слово. Используется для ограничений:
*
*
*
📌 Особенно важно для immutability и thread-safety.
🧯
Блок в конструкции
💡 Используется для освобождения ресурсов: закрытия потоков, соединений и т.д.
⚰️
Метод из
⚠️ УСТАРЕЛ с Java 9, удалён в Java 18. Не используй.
🔪 Непредсказуем, плохо работает, тормозит GC. Вместо него —
🧠 Важно не путать:
*
*
*
📲 Мы в MAX
👉@BookJava
final, finally, finalize — но смысл у них совершенно разный. Разберёмся 🧠🔒
finalКлючевое слово. Используется для ограничений:
*
final class — нельзя наследовать.*
final method — нельзя переопределить.*
final variable — нельзя изменить значение (один раз присвоил — всё).📌 Особенно важно для immutability и thread-safety.
final int x = 10;
x = 20; // ошибка компиляции
🧯
finallyБлок в конструкции
try-catch-finally. Выполняется всегда, даже если был return или exception.💡 Используется для освобождения ресурсов: закрытия потоков, соединений и т.д.
try {
// что-то может выбросить исключение
} catch (Exception e) {
// обработка ошибки
} finally {
// всегда выполнится
}
⚰️
finalize()Метод из
Object, вызывался перед удалением объекта сборщиком мусора.⚠️ УСТАРЕЛ с Java 9, удалён в Java 18. Не используй.
🔪 Непредсказуем, плохо работает, тормозит GC. Вместо него —
AutoCloseable и try-with-resources.
@Override
protected void finalize() throws Throwable {
System.out.println("До свидания...");
}
🧠 Важно не путать:
*
final — про нельзя менять*
finally — про всегда выполнится*
finalize — про устаревший и бесполезный метод📲 Мы в MAX
👉@BookJava
❤4🔥2👍1
🧠 Dependency Inversion Principle (D в SOLID)
Зависимости должны идти от высокоуровневой политики к низкоуровневым деталям, а не наоборот. Абстракции — хозяева, реализации — обслуживающий персонал.
📌 Коротко о сути
* Модули верхнего уровня (бизнес-логика) зависят только от интерфейсов/абстракций.
* Модули нижнего уровня (база, сеть, файлы) также зависят от тех же абстракций.
* Сами абстракции не знают ничего о деталях, тем самым разрывая «бетонную» сцепку между слоями.
💡 Мини-пример (Java 17+, Spring Boot 3+)
BillingService может жить в модульном jar без spring-email-starter и SMTP-кода — протестировать его теперь элементарно.
⚠️ Где рождаются проблемы
1. Путаница DI container ≠ DIP
IoC/DI-фреймворк (Spring, CDI) — лишь удобный способ «сращивать» зависимости, но принцип работает и без контейнера (чистый constructor injection).
2. Абстракции ради галочки
Интерфейс OneImplService с единственной реализацией ломает читаемость, тесты и автоконфиг 📉.
➜ Создавай абстракцию, когда реально нужны сменяемость, тестируемость или расширяемость.
3. Утечки деталей
Если интерфейс таскает DTO из слоя хранения, ты всё ещё «протёк» к базе. Держи контракты чистыми.
4. Слепая вера в фреймворк
Жизненный цикл бинов, прокси, lazy-init — магия мешает понимать, кто кем владеет.
➜ Всегда можешь собрать объект вручную в юнит-тесте. Если сложно — запах нарушения DIP.
5. Слишком много уровней абстракций
«Контроллер → сервис → менеджер → порт → адаптер → репозиторий» превращает код в матрёшку. Дизайн важнее количества слоёв.
📝 Практические советы
* Используй constructor injection по умолчанию. Поле
* Группируй интерфейсы по use-case, а не по технологии (например,
* В тестах не мокай фреймворк — мокай контракт.
* Для одноразовых реализаций начни с
* Проверка себя: можно ли запустить модуль верхнего уровня без нижнего? Если да — DIP соблюдён.
💬 Итог
DIP — это не про «везде интерфейсы» и не про «подключи Spring». Это про правильное направление зависимостей, которое делает код гибким, тестируемым и не заложником технологий. А проблемы возникают, когда путают инструмент с принципом и забывают, что абстракция должна прятать детали, а не выпячивать их.
📲 Мы в MAX
👉@BookJava
Зависимости должны идти от высокоуровневой политики к низкоуровневым деталям, а не наоборот. Абстракции — хозяева, реализации — обслуживающий персонал.
📌 Коротко о сути
* Модули верхнего уровня (бизнес-логика) зависят только от интерфейсов/абстракций.
* Модули нижнего уровня (база, сеть, файлы) также зависят от тех же абстракций.
* Сами абстракции не знают ничего о деталях, тем самым разрывая «бетонную» сцепку между слоями.
💡 Мини-пример (Java 17+, Spring Boot 3+)
// 1️⃣ Абстракция — контракт
public interface NotificationSender {
void send(Message msg);
}
// 2️⃣ Верхний уровень — бизнес-служба
@Service
public class BillingService {
private final NotificationSender sender;
public BillingService(NotificationSender sender) {
this.sender = sender; // зависим от контракта
}
public void bill(Client c) {
// ...
sender.send(new Message("Invoice #42"));
}
}
// 3️⃣ Низкий уровень — деталь
@Component
public class EmailSender implements NotificationSender {
public void send(Message msg) {
// SMTP-магия
}
}
BillingService может жить в модульном jar без spring-email-starter и SMTP-кода — протестировать его теперь элементарно.
⚠️ Где рождаются проблемы
1. Путаница DI container ≠ DIP
IoC/DI-фреймворк (Spring, CDI) — лишь удобный способ «сращивать» зависимости, но принцип работает и без контейнера (чистый constructor injection).
2. Абстракции ради галочки
Интерфейс OneImplService с единственной реализацией ломает читаемость, тесты и автоконфиг 📉.
➜ Создавай абстракцию, когда реально нужны сменяемость, тестируемость или расширяемость.
3. Утечки деталей
Если интерфейс таскает DTO из слоя хранения, ты всё ещё «протёк» к базе. Держи контракты чистыми.
4. Слепая вера в фреймворк
Жизненный цикл бинов, прокси, lazy-init — магия мешает понимать, кто кем владеет.
➜ Всегда можешь собрать объект вручную в юнит-тесте. Если сложно — запах нарушения DIP.
5. Слишком много уровней абстракций
«Контроллер → сервис → менеджер → порт → адаптер → репозиторий» превращает код в матрёшку. Дизайн важнее количества слоёв.
📝 Практические советы
* Используй constructor injection по умолчанию. Поле
final = явная зависимость.* Группируй интерфейсы по use-case, а не по технологии (например,
TransferPort, а не JdbcTransferRepository).* В тестах не мокай фреймворк — мокай контракт.
* Для одноразовых реализаций начни с
class. Если появится второй вариант — быстро вынесешь интерфейс (IDE поможет).* Проверка себя: можно ли запустить модуль верхнего уровня без нижнего? Если да — DIP соблюдён.
💬 Итог
DIP — это не про «везде интерфейсы» и не про «подключи Spring». Это про правильное направление зависимостей, которое делает код гибким, тестируемым и не заложником технологий. А проблемы возникают, когда путают инструмент с принципом и забывают, что абстракция должна прятать детали, а не выпячивать их.
📲 Мы в MAX
👉@BookJava
👍4❤2🔥1
📌 Stream API: забудьте про peek()
Метод
⚠️ Почему не стоит полагаться на peek()?
*
* Поведение зависит от терминальной операции: например, при некоторых оптимизациях JDK (особенно в параллельных стримах) вызов
💡 Пример опасного кода:
🧠 Лучший подход: используйте
✅ Правильная замена:
Если нужно выполнить действие над каждым элементом, используйте явный и безопасный подход:
Или вообще вынесите логику за пределы стрима.
🧹 Держите код понятным и безопасным — забудьте про
📲 Мы в MAX
👉@BookJava
Метод
peek() в Java Stream API выглядит удобным для отладки, но его использование в реальном коде часто приводит к проблемам.⚠️ Почему не стоит полагаться на peek()?
*
peek() — промежуточная операция, которая не гарантирует вызов функции для каждого элемента стрима.* Поведение зависит от терминальной операции: например, при некоторых оптимизациях JDK (особенно в параллельных стримах) вызов
peek() может быть пропущен или работать непредсказуемо.💡 Пример опасного кода:
List<String> names = Stream.of("Java", "Spring", "Hibernate")
.peek(System.out::println) // 👈 анти-паттерн!
.collect(Collectors.toList());
🧠 Лучший подход: используйте
peek() исключительно для дебага, но никогда — для изменения состояния или важных операций.✅ Правильная замена:
Если нужно выполнить действие над каждым элементом, используйте явный и безопасный подход:
List<String> names = Stream.of("Java", "Spring", "Hibernate")
.map(name -> {
log.info("Processing: {}", name); // или другая логика
return name;
})
.collect(Collectors.toList());
Или вообще вынесите логику за пределы стрима.
🧹 Держите код понятным и безопасным — забудьте про
peek().📲 Мы в MAX
👉@BookJava
🔥3👍1👎1
📌 Обход коллекций в Java: делаем правильно!
Часто вижу, как разработчики по привычке используют старый подход с циклом
🧠 1. Foreach (Java 5+)
Коротко, ясно, подходит для большинства задач:
🧠 2. Метод forEach (Java 8+)
Отлично подходит для функционального стиля:
⚠️ Важно: нельзя модифицировать саму коллекцию во время такого обхода!
🧠 3. Stream API (Java 8+)
Идеален для сложной обработки с фильтрацией, маппингом и т.д.:
💡 Что выбрать?
* Простой перебор? Используй цикл foreach.
* Нужно быстро и функционально?
* Сложные манипуляции? Только Stream API.
📌 Используйте современные подходы, это читаемость и удобство поддержки вашего кода!
📲 Мы в MAX
👉@BookJava
Часто вижу, как разработчики по привычке используют старый подход с циклом
for. Сегодня напомню оптимальные способы перебора коллекций в Java.🧠 1. Foreach (Java 5+)
Коротко, ясно, подходит для большинства задач:
for (String item : collection) {
// обработка элемента
}
🧠 2. Метод forEach (Java 8+)
Отлично подходит для функционального стиля:
collection.forEach(item -> {
// обработка элемента
});
⚠️ Важно: нельзя модифицировать саму коллекцию во время такого обхода!
🧠 3. Stream API (Java 8+)
Идеален для сложной обработки с фильтрацией, маппингом и т.д.:
collection.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.forEach(System.out::println);
💡 Что выбрать?
* Простой перебор? Используй цикл foreach.
* Нужно быстро и функционально?
collection.forEach().* Сложные манипуляции? Только Stream API.
📌 Используйте современные подходы, это читаемость и удобство поддержки вашего кода!
📲 Мы в MAX
👉@BookJava
👍2