Библиотека джависта | Java, Spring, Maven, Hibernate
22.5K subscribers
2.36K photos
52 videos
48 files
3.44K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
👑 IntelliJ IDEA: продвинутый дебаг

Представь, что есть цикл на 10 000 элементов, и баг воспроизводится только на одном конкретном объекте. Без Conditional Breakpoints придется жать F8 вручную сотни раз. А с ними дебаггер сам остановится в нужный момент.

🔹 Как включить


Кликни правой кнопкой на кружок брейкпоинта → появится поле Condition. Введите любое булево Java-выражение. Всё, дебаггер будет останавливаться только когда оно true.

🔹 Примеры из реальной жизни

▪️ Фильтрация по ID
user.getId() == 42

Останавливаемся только на конкретном пользователе — удобно при обработке списка сущностей из БД.

▪️ Фильтрация по содержимому строки
request.getUrl().contains("/admin")

Отлавливаем только определённые HTTP-запросы в фильтре или интерцепторе.

▪️ Ловим NPE до того, как он случился
order.getItems() == null

Останавливаемся именно тогда, когда данные уже сломаны, а не после падения.

▪️ Условие по индексу в цикле
i == 999

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

🔹 Продвинутые трюки

— Log message вместо остановки. Если не хотите прерывать выполнение, а просто логировать — в том же окне брейкпоинта включите "Evaluate and log" и введите выражение.

— Pass count. Чуть ниже в настройках брейкпоинта есть поле "Pass count" — брейкпоинт сработает только на N-м попадании.

— Disable until hit. Можно настроить цепочку: один брейкпоинт активирует другой. В настройках есть "Disable until breakpoint is hit" — указываете другой брейкпоинт, и текущий начнёт работать только после срабатывания того.

⚠️ Condition вычисляется на каждом попадании в брейкпоинт — это вызов на стороне JVM. В горячих местах (tight loop, высоконагруженный метод) это может заметно тормозить приложение.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍143🔥1👏1
🧵 StampedLock: когда ReentrantReadWriteLock уже не справляется

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

Поток, который хочет писать, ждёт пока все читатели закончат. При высокой read-нагрузке писатель может ждать очень долго. StampedLock решает именно это.

🔵 Три режима, не два

ReentrantReadWriteLock даёт два режима — read и write. StampedLock добавляет третий — optimistic read.

StampedLock lock = new StampedLock();

// обычный read lock
long stamp = lock.readLock();
try {
// читаем
} finally {
lock.unlockRead(stamp);
}

// write lock
long stamp = lock.writeLock();
try {
// пишем
} finally {
lock.unlockWrite(stamp);
}


Это знакомо. Но вот где начинается интересное:
long stamp = lock.tryOptimisticRead();
// читаем данные без блокировки вообще
int value = this.value;

if (!lock.validate(stamp)) {
// кто-то писал пока мы читали — перечитываем под реальным локом
stamp = lock.readLock();
try {
value = this.value;
} finally {
lock.unlockRead(stamp);
}
}

tryOptimisticRead() не захватывает никакого лока, а просто возвращает штамп. validate() проверяет — была ли запись с момента получения штампа. Если нет, то данные консистентны, идём дальше, если да, то фолбэк на обычный read lock.

🔵 Что такое stamp

Это long, который кодирует состояние лока. Каждая успешная запись инвалидирует все оптимистичные штампы. Поэтому validate() — это просто битовая проверка, без CAS, без синхронизации, так что она очень дешёвая.

🔵 Где это реально даёт профит

Сценарий: данные читаются в 95% случаев, пишутся редко. Классический пример — кеш, конфигурация, счётчики с инкрементом.
С ReentrantReadWriteLock все читатели всё равно захватывают shared lock — это операция на AQS, есть накладные расходы. С StampedLock + optimistic read при отсутствии конкурентных записей блокировки нет вообще.

🔵 Подводные камни

— StampedLock не реентерабельный. Если поток захватил write lock и попытается захватить его снова — дедлок. Это принципиальное отличие от ReentrantReadWriteLock.

— Нет поддержки Condition. Если тебе нужен await/signal — StampedLock не подойдёт.

— Нет поддержки прерывания в обычных методах readLock()/writeLock(). Есть отдельные readLockInterruptibly() — но это уже осознанный выбор.

StampedLock — инструмент для конкретного профиля нагрузки: много чтений, редкие записи, нет реентерабельности. Не замена всем локам подряд. Но в правильном месте даёт ощутимый прирост без усложнения архитектуры.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥5👏1
😁 Развернем холиварчик

К статье на Хабре «Я два месяца платил 300к человеку, который тихо скармливал мои задачи в ChatGPT» уже почти 900 комментов.

Прочитайте, давайте тоже обсудим 💬

🐸 Библиотека джависта

#DevLife
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8👍1🔥1
🔨 Паттерн Builder

Builder — порождающий паттерн, который разделяет конструирование сложного объекта и его представление. Позволяет создавать объекты пошагово и получать разные результаты, используя одинаковый процесс построения.

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

🔹 Когда конструктор принимает много параметров — особенно если часть из них опциональна.
🔹 Когда порядок шагов построения важен или требует валидации перед созданием.
🔹 Когда нужно создавать разные представления одного и того же объекта.

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

1️⃣ Читаемость и явное намерение

Конструктор с 7 параметрами — это загадка. Builder с именованными шагами — спецификация. Читая вызов, вы сразу понимаете, что создаётся и почему.

2️⃣ Безопасная валидация

Вся проверка параметров сосредоточена в методе build(). Объект либо создаётся корректным, либо не создаётся вообще. Нет промежуточных невалидных состояний.

3️⃣ Иммутабельность

Builder накапливает параметры, build() создаёт финальный неизменяемый объект. Это удобно в многопоточном коде и делает объекты предсказуемыми.

4️⃣ Поддержка опциональных параметров без перегрузок

Вместо пяти перегруженных конструкторов — один Builder. Указываете только то, что нужно, остальное берёт дефолт.

⚠️ Lombok даёт @Builder за одну аннотацию — но понимать механику всё равно стоит.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍32🤔1
😮 Топ-вакансий для джавистов за неделю

Java Developer - офис (Санкт-Петербург)

Middle (Middle+) Backend Developer - офис (Санкт-Петербург)

Middle/Senior Java Developer - офис (Москва)

➡️ Еще больше топовых вакансий — в нашем канале Java jobs
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱21
В чём разница между Statement и PreparedStatement?

— Statement используется для выполнения простых SQL-запросов без параметров. Он формирует запрос как строку и каждый раз компилирует его заново, что может быть медленно и небезопасно.

— PreparedStatement предварительно компилируется базой данных и позволяет задавать параметры через плейсхолдеры. Что повышает производительность при многократном выполнении одного запроса и защищает от SQL-инъекций (данные не конкатенируются со строкой запроса, а подставляются корректно).

Поэтому в реальных проектах почти всегда используют PreparedStatement.

🐸 Библиотека собеса по Java

#core
Please open Telegram to view this post
VIEW IN TELEGRAM
👍53🔥1
🐸 Библиотека джависта

#DevLife
Please open Telegram to view this post
VIEW IN TELEGRAM
😁14😢3💯1
♻️ Redis-кеш с AI

@Cacheable над методом поставить несложно. Сложнее объяснить тимлиду почему прод лежит, когда Redis недоступен — а CacheErrorHandler никто не написал.

Промпт, который закрывает этот пробел сразу:

Ты Senior Java-разработчик. Сгенерируй модуль кеширования для Spring Boot 3.x с Redis.

Требования:
- RedisCacheConfiguration с GenericJackson2JsonRedisSerializer
- CacheManager с раздельными TTL для разных кешей (RedisCacheManagerBuilder)
- 2 сервиса с @Cacheable, @CachePut, @CacheEvict — с комментарием когда что применять
- Кастомный CacheErrorHandler (деградация без исключения при недоступном Redis)
- application.yml конфиг
- Тест: @SpringBootTest + Testcontainers Redis

Стек: Java 21, Spring Boot 3.2, Spring Data Redis, Testcontainers 1.19

@EnableCaching — только в отдельном @Configuration, не в Application-классе.

Верни код классов, yml, тест.


══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43😁3🔥1
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует

Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.

Как адаптировать продукт и не исчезнуть из выдачи:

— интегрировать MCP и A2A-взаимодействие, чтобы агенты могли вас читать;
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.

Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.

Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.

Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
🥱2
👑 IntelliJ IDEA: Evaluate Expression

Открывается через Run → Evaluate Expression или Alt + F8 (Option + F8 на macOS).

🔹 Что это вообще такое


Небольшое окно с полем ввода, в котором можно написать любое Java-выражение и мгновенно получить результат. Код выполняется в контексте текущего фрейма — видны все локальные переменные, this, поля класса.

🔹 Примеры из реальной жизни


▪️ Вызов метода на живом объекте

userService.findById(42L)

Не нужно писать тест — просто вызываете метод и смотрите, что он реально вернёт с текущими данными в БД.

▪️ Проверка сложного условия
order.getItems().stream()
.anyMatch(i -> i.getPrice().compareTo(BigDecimal.ZERO) < 0)

Подозреваете, что в заказе есть товары с отрицательной ценой? Проверяете прямо сейчас, не добавляя логов.

▪️ Изменение переменной на лету

user.setRole(Role.ADMIN)

Хотите проверить, как поведёт себя код с другим значением — меняете стейт прямо в памяти и продолжаете выполнение. Приложение не перезапускается.

▪️ Десериализация или парсинг

objectMapper.writeValueAsString(response)

Получаете красивый JSON из объекта прямо в дебаггере — удобно проверить, что именно уйдёт клиенту.

🔹 Продвинутые трюки

— В окне Evaluate есть переключатель с однострочного режима на Code Fragment — там можно писать многострочный код с переменными, условиями и циклами.

— Результат вычисления отображается как обычная переменная в дереве дебаггера. Можно раскрыть, посмотреть вложенные поля, скопировать значение.

⚠️ Evaluate выполняет код реально — если вызовете метод с side-effect (запись в БД, отправка события), это произойдёт по-настоящему.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍114👏1
✔️ Java-тест: утечка в фильтре

Memory leak, который живёт неделями и не воспроизводится локально 👇

📦 Задание — code review


Написали фильтр для аудита запросов. В продакшне через несколько дней — OutOfMemoryError.

@Component
public class AuditFilter implements Filter {

private static final ThreadLocal<AuditContext> CONTEXT =
new ThreadLocal<>();

@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {

AuditContext ctx = new AuditContext(
((HttpServletRequest) request).getRequestURI(),
System.currentTimeMillis()
);
CONTEXT.set(ctx);

try {
chain.doFilter(request, response);
} finally {
auditLog(CONTEXT.get());
}
}

public static AuditContext current() {
return CONTEXT.get();
}

private void auditLog(AuditContext ctx) {
// пишем в БД...
}
}


▪️ Объясни

— Почему объекты в ThreadLocal не собираются GC, даже если запрос завершён.
— Какая одна строчка кода здесь отсутствует, и где именно она должна быть.

* Есть ли риски при использовании ThreadLocal в virtual threads (Project Loom)?

Ставьте → 🔥, если нравится формат. Если нет → 🌚

💬 Решения под спойлер. Сравним, какое будет лучше.

🐸 Библиотека собеса по Java

#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍21
🧵 GC: как выбрать сборщик мусора и не пожалеть

Выбирал ли ты GC хоть раз осознанно? Часто запустил приложение — работает, и ладно. Но когда начинаются проблемы с латентностью или throughput, первый вопрос от SRE: «А какой у вас GC?».

🔹 Разберём что выбрать и почему.

Все GC балансируют между двумя вещами:

▪️ Throughput — сколько полезной работы делает приложение за единицу времени
▪️ Latency — насколько долго приложение паузится на GC

Идеального нет. Выбор всегда зависит от профиля нагрузки.

🔹 Serial и Parallel — если латентность не важна

-XX:+UseSerialGC / -XX:+UseParallelGC


Serial GC однопоточный. И Minor, и Major GC выполняет один поток, всё приложение стоит. Звучит как антиквариат, но на маленьком heap (< 256MB) и однопоточной среде накладные расходы на координацию потоков не нужны, Serial реально быстрее.

Parallel GC — то же самое, но сборку делают несколько потоков одновременно. STW никуда не девается, просто сама пауза короче за счёт параллелизма. На Major GC всё равно можешь получить сотни миллисекунд.

Где уместно: batch-обработка, утилиты, CLI-инструменты. Там где важен максимальный throughput и никто не ждёт response time.

🔹 G1 — дефолт с Java 9

-XX:+UseG1GC


G1 делит heap на регионы фиксированного размера (~1-32MB) и собирает их инкрементально. Не нужно собирать весь heap за раз — выбираются регионы с наибольшим количеством мусора (отсюда "Garbage First").

Главная настройка:
bash-XX:MaxGCPauseMillis=200  # целевое время паузы


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

Где уместно: большинство серверных приложений с heap от 4GB. Хороший баланс throughput/latency без тюнинга.

🔹 ZGC и Shenandoah — когда паузы недопустимы

-XX:+UseZGC / -XX:+UseShenandoahGC


Оба делают большую часть работы конкурентно — параллельно с работой приложения. STW-паузы измеряются единицами миллисекунд вне зависимости от размера heap.

Где уместно: real-time системы, trading, API с жёсткими SLA по p99 латентности. Платишь немного throughput — получаешь предсказуемые паузы.

🔹 Как выбирать на практике

Не угадывать, а мерить. Включаешь GC логи и смотришь что происходит:
bash-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=20m

Дальше либо GCEasy, либо руками смотришь на паузы и частоту сборок.

🔹 Три вопроса которые определяют выбор

1. Какой размер heap?
До 4GB — G1 справится. Больше 16GB — смотри на ZGC, у G1 начинаются долгие Mixed GC.

2. Какие требования к латентности?
Есть SLA на p99 < 50ms — только ZGC или Shenandoah. Нет жёстких требований — G1.

3. Какой профиль аллокаций?
Много short-lived объектов — генерационный GC справится хорошо. Много long-lived крупных объектов — G1 может давать длинные паузы на evacuation, ZGC справится лучше.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83🔥1
🖥 JDK 21 или 25, что выбрать новичку

Вышла Java 25, которую все хотят попробовать. Но ставить ли её или взять проверенную Java 21 LTS?

В этом гайде — пошаговая установка Java на Windows с картинками, разбор различий между версиями, настройка JAVA_HOME и PATH.

Ориентирован для начинающих разработчиков, изучающих Java

🔗 Читайте в статье

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍1