Библиотека Java разработчика
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
Ko'proq ko'rsatish📈 Telegram kanali Библиотека Java разработчика analitikasi
Библиотека Java разработчика (@bookjava) Rus til segmentidagi kanali faol ishtirokchi. Hozirda hamjamiyat 10 280 obunachidan iborat bo'lib, Texnologiyalar & Aralashmalar toifasida 12 030-o'rinni va Rossiya mintaqasida 63 913-o'rinni egallagan.
📊 Auditoriya ko‘rsatkichlari va dinamika
невідомо sanasidan buyon loyiha tez o‘sib, 10 280 obunachiga ega bo‘ldi.
05 Iyun, 2026 dagi oxirgi ma’lumotlarga ko‘ra kanal barqaror faollikka ega. Oxirgi 30 kunda obunachilar soni 20 ga, so‘nggi 24 soatda esa 0 ga o‘zgardi va umumiy qamrov yuqori darajada qolmoqda.
- Tasdiqlash holati: Tasdiqlanmagan
- Jalb etish (ER): Auditoriya o‘rtacha 8.29% darajada jalb etiladi. Nashrdan keyingi dastlabki 24 soatda kontent odatda umumiy obunachilar sonining 3.77% ini tashkil etuvchi reaksiyalarni to‘playdi.
- Post qamrovi: Har bir post o‘rtacha 852 marta ko‘riladi; birinchi sutkada odatda 388 ta ko‘rish yig‘iladi.
- Reaksiyalar va o‘zaro ta’sir: Auditoriya faol: har bir postga o‘rtacha 6 ta reaksiya keladi.
- Tematik yo‘nalishlar: Kontent string, интерфейс, строка, boot, api kabi asosiy mavzularga jamlangan.
📝 Tavsif va kontent siyosati
Muallif resursni shaxsiy fikrni ifoda etish maydoni sifatida ta’riflaydi:
“📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.
По всем вопросам @evgenycarter
РКН clck.ru/3KoGeP”
Yuqori yangilanish chastotasi (oxirgi ma’lumot 06 Iyun, 2026 da olingan) sababli kanal doimo dolzarb va katta qamrovli bo‘lib qoladi. Analitika auditoriya kontent bilan faol hamkorlik qilishini, uni Texnologiyalar & Aralashmalar toifasidagi muhim ta’sir nuqtasiga aylantirishini ko‘rsatadi.
@PostConstruct — особенно в проде
Сейчас расскажу, почему инициализировать важную бизнес-логику в @PostConstruct — плохая идея.
Типичный пример:
@Component
public class CacheLoader {
private final SomeService service;
public CacheLoader(SomeService service) {
this.service = service;
}
@PostConstruct
public void init() {
service.loadDataIntoCache(); // ⚠️ обращение к БД
}
}
🧨 Проблема: @PostConstruct вызывается до того, как приложение полностью поднялось.
Если внутри будет ошибка (например, БД недоступна) — приложение может упасть, или что хуже — запуститься в полурабочем состоянии.
📌 Кроме того:
* ❌ Нет контроля над порядком выполнения таких методов;
* ❌ Нельзя легко переиспользовать эту логику (например, вручную перезагрузить кеш);
* ❌ В тестах или dev-среде — такие вызовы часто мешают.
✅ Современный подход — использовать ApplicationListener:
@Component
public class CacheLoader implements ApplicationListener<ApplicationReadyEvent> {
private final SomeService service;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
service.loadDataIntoCache(); // 👍 вызывается только после старта
}
}
📌 Альтернатива — аннотация @EventListener:
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// безопасно загружаем данные
}
📦 В Spring Boot это нативный и рекомендованный способ выполнения кода после старта.
🧠 Резюме:
🔹 @PostConstruct — только для простой инициализации бинов.
🔹 Бизнес-логику и I/O — в @EventListener(ApplicationReadyEvent.class).
👉@BookJavaOptional.map() и методами, возвращающими Optional
Многие Java-разработчики на автомате пишут что-то вроде:
Optional<User> user = findUserById(id); // возвращает Optional<User>
Optional<String> email = user.map(User::getEmail); // getEmail тоже возвращает Optional<String>
⚠️ Проблема: map() в Optional не "разворачивает" вложенные Optional. В этом примере email будет типа Optional<Optional<String>>, что почти всегда нежелательно.
📌 Правильный способ — использовать flatMap():
Optional<String> email = user.flatMap(User::getEmail);
💡 flatMap() позволяет избежать "двойной обёртки", если метод внутри map() уже возвращает Optional.
🔁 Аналогичная ситуация с Stream.map() — если внутри map() вызывается метод, возвращающий Stream, то получится Stream<Stream<T>>, и опять же нужно использовать flatMap().
🧪 Мини-памятка:
* map() — когда метод возвращает обычный тип (T → R);
* flatMap() — когда метод возвращает Optional или Stream (T → Optional<R> или T → Stream<R>).
👉@BookJavaExecutorService. Но вот что важно помнить:
📌 shutdownNow() — опасная ловушка при работе с виртуальными потоками.
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> future = executor.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
});
executor.shutdownNow(); // ❗️Ничего не произойдёт
💡 Почему?
Метод shutdownNow() не может прервать виртуальные потоки, если они запущены через Executors.newVirtualThreadPerTaskExecutor(). Он лишь помечает пул как завершённый и возвращает список задач, которые ещё не стартовали.
Уже запущенные виртуальные потоки продолжают выполняться — Thread.interrupt() не работает, потому что у виртуальных потоков отсутствует связь с ThreadGroup, к которому привязан shutdownNow.
⚠️ Следствие:
Если вы рассчитываете, что shutdownNow() "остановит всё" — вы можете получить утечку задач или зависания.
🔧 Что делать?
1. Контролируйте завершение через Future.cancel(true) — он вызывает interrupt() на конкретной задаче.
2. Стройте явную кооперативную модель отмены — с флагами или Thread.interrupted() внутри задачи.
3. Для массовой отмены — храните Future задач и отменяйте вручную.
👉@BookJava@Transactional на private - методах не работает?
Да, Spring просто не применяет прокси к private-методам. Это частый баг, который трудно отловить: ты вызываешь приватный метод внутри бина, а транзакция… не начинается 🤷♂️
📌 Почему так происходит?
Spring AOP по умолчанию использует динамические прокси (JDK или CGLIB), которые перехватывают внешние вызовы. А вызов private - метода из того же класса — это внутренний вызов, который обходит прокси.
Пример, который НЕ работает:
@Service
public class UserService {
public void createUser() {
saveUser(); // Вызов мимо прокси 😞
}
@Transactional
private void saveUser() {
// Транзакция НЕ начнется!
}
}
💡 Как правильно:
1. Сделай метод public или хотя бы protected,
2. Или выноси в отдельный бин:
@Service
public class UserService {
private final TxUserSaver txUserSaver;
public UserService(TxUserSaver txUserSaver) {
this.txUserSaver = txUserSaver;
}
public void createUser() {
txUserSaver.saveUser(); // Теперь через прокси ✅
}
}
@Service
public class TxUserSaver {
@Transactional
public void saveUser() {
// Всё сработает как надо
}
}
⚠️ Проверь свои сервисы — ты можешь удивиться, сколько транзакций у тебя не работают. Особенно в проектах, где @Transactional ставят "на всякий случай".
👉@BookJavaequals()/hashCode() может привести к потере данных при работе с HashMap.
Допустим, у вас есть Entity:
public class User {
private String id;
private String name;
// equals/hashCode только по id
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Вроде норм. Но теперь добавим такого юзера в HashMap, а потом... изменим его id:
User user = new User();
user.setId("1");
user.setName("Alice");
Map<User, String> map = new HashMap<>();
map.put(user, "value");
user.setId("2"); // ⚠️ ключ стал "невидимым"
System.out.println(map.get(user)); // null 😱
📌 Почему так происходит?
HashMap ищет ключ по hashCode() → ищет бакет → сравнивает через equals(). А hashCode() уже другой, и объект "теряется".
💡 Совет: если вы используете объект как ключ в мапе или добавляете его в Set, не изменяйте его поля, участвующие в equals()/hashCode()!
📌 А как правильно?
- Делайте такие поля final;
- Или используйте неизменяемые типы (record);
- Или не используйте такие объекты как ключи вовсе.
Вот безопасный вариант с record:
public record User(String id, String name) {}
👉@BookJava@OneToMany, Hibernate часто делает это лениво (LAZY), но при первом доступе — забирает всю коллекцию целиком.
Это может привести к OutOfMemoryError или резкому проседанию производительности.
📌 Решение: использовать пагинацию (batch-size) или запрос коллекции порциями.
Как настроить batch-size на уровне сущности:
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 50)
private List<Order> orders;
}
🧠 Теперь Hibernate будет загружать за раз по 50 элементов, а не всю коллекцию сразу!
📌 Или можно настроить глобально через application.properties:
spring.jpa.properties.hibernate.default_batch_fetch_size=50
⚠️ Важно:
- @BatchSize работает только для LAZY-связей.
- Это не пагинация в SQL, а оптимизация внутренних запросов Hibernate.
- Если коллекция огромная (100k+ записей) — лучше делать явные paged запросы в репозитории.
💡Помните: без настройки batch-size Hibernate может сломать приложение под нагрузкой. Оптимизируйте загрузку коллекций заранее!
👉@BookJava
class Parent {
int a = 5;
{
System.out.println("Parent instance initializer");
a = 10;
}
Parent() {
System.out.println("Parent constructor, a = " + a);
}
}
class Child extends Parent {
int b = 15;
{
System.out.println("Child instance initializer");
b = 25;
}
Child() {
System.out.println("Child constructor, b = " + b);
}
}
public class Test {
public static void main(String[] args) {
Child child = new Child();
}
}
Вывод программы:
Parent instance initializer Parent constructor, a = 10 Child instance initializer Child constructor, b = 25Пошаговое выполнение: 1. Сначала загружается родительский класс
Parent.
2. Выполняется:
- Инициализация полей родителя (a = 5),
- Потом instance initializer блока родителя (a = 10),
- Потом конструктор родителя (Parent()).
3. Далее переходим к дочернему классу Child:
- Инициализация полей дочернего класса (b = 15),
- Потом instance initializer блока дочернего класса (b = 25),
- Потом конструктор дочернего класса (Child()).
Важный порядок действий:
1. Инициализация родителя → 2. Конструктор родителя → 3. Инициализация потомка → 4. Конструктор потомка.
Блоки инициализации всегда выполняются до тела конструктора, но после вызова super().
👉@BookJavasuper()), но до тела конструктора текущего класса.
Порядок инициализации:
1. Сначала инициализируются поля в порядке их объявления.
2. Затем выполняются instance initializer blocks, в том порядке, в котором они написаны в коде.
3. После этого выполняется тело конструктора.
Пример:
class Example {
int x = 10;
{
System.out.println("Instance initializer block");
x = 20;
}
Example() {
System.out.println("Constructor");
System.out.println("x = " + x);
}
public static void main(String[] args) {
Example ex = new Example();
}
}
Вывод:
Instance initializer block Constructor x = 20Ключевые моменты: - Статические блоки (
static {}) — другое дело: они выполняются один раз при загрузке класса.
- Instance initializer blocks полезны для общей инициализации, которую нужно выполнять вне зависимости от того, какой конструктор вызывается.
👉@BookJava@RequestParam на пустоту или формат. Это шумит код и приводит к ошибкам.
Вместо этого используйте аннотации валидации прямо на параметрах:
@RestController
@RequestMapping("/api")
@Validated // Обязательно!
public class UserController {
@GetMapping("/users")
public List<User> getUsers(
@RequestParam @NotBlank String name,
@RequestParam @Min(18) int age
) {
// Если валидация не пройдена — автоматически вернётся 400 Bad Request
return userService.findUsers(name, age);
}
}
📌 Ключевые моменты:
- Обязательно ставим @Validated над классом контроллера.
- На параметры добавляем любые стандартные аннотации из jakarta.validation.constraints.
- Ошибки валидации Spring обработает автоматически через MethodArgumentNotValidException.
💡 Можно кастомизировать ответ на ошибку, добавив глобальный @ExceptionHandler.
⚠️ Без @Validated аннотации на контроллере валидация параметров работать не будет!
👉@BookJava@Transactional внутри того же класса не запускает новую транзакцию.
Почему? Spring оборачивает бин в прокси, а внутренние вызовы проходят мимо прокси, значит, аннотация игнорируется.
Пример проблемы:
@Service
public class OrderService {
@Transactional
public void createOrder() {
saveOrder();
}
@Transactional
public void saveOrder() {
// Новый транзакционный контекст не создастся!
}
}
💡 Как правильно:
1. Вынести saveOrder() в отдельный бин.
2. Или получить прокси текущего бина через AopContext:
@Service
@EnableAspectJAutoProxy(exposeProxy = true) // важно!
public class OrderService {
@Transactional
public void createOrder() {
((OrderService) AopContext.currentProxy()).saveOrder();
}
@Transactional
public void saveOrder() {
// Теперь всё ок ✅
}
}
⚠️ Важный момент: exposeProxy = true нужен на уровне конфигурации, иначе AopContext не заработает.
Понимание этой тонкости критично для корректного управления транзакциями в Spring! 🚀
👉@BookJavaOptional, который часто ловит даже опытных.
Многие пишут так:
Optional<String> optional = getValue();
if (optional.isPresent()) {
doSomething(optional.get());
}
⚠️ Это антипаттерн! Вы теряете суть Optional и рискуете ошибками в многопоточке.
📌 Правильный способ — использовать функциональный стиль:
getValue().ifPresent(this::doSomething);
Или ещё элегантнее:
getValue()
.map(this::transform)
.filter(this::isValid)
.ifPresent(this::doSomething);
💡 Подходы:
- map для преобразования значения
- filter для отсеивания ненужных
- orElse, orElseGet, orElseThrow для обработки отсутствия
- ifPresentOrElse в Java 9+ для двух вариантов действий
📈 Выгоды:
- Код становится компактнее
- Безопаснее при рефакторинге
- Лучше для чтения в потоковых операциях (Stream API)
🛠 Если нужно вынуть значение — используйте orElseThrow() вместо .get():
String value = getValue().orElseThrow(() -> new IllegalStateException("Value not found"));
👉 Отказывайтесь от .isPresent() и .get() связки — используйте силу функционального подхода! 🚀
👉@BookJava
Endi mavjud! Telegram Tadqiqoti 2025 — yilning asosiy insaytlari 
