🤖 А ты справишься с тестом по Kotlin?
🏆 Пройди тест из 10 вопросов, проверь свой уровень знаний и получи скидку на онлайн-курс «Kotlin Backend Developer. Professional» от OTUS!
Если успешно пройдешь тест, сможешь забронировать место в группе по выгодной цене! И еще дарим промокодKotlin5
🎫 Курс можно приобрести в рассрочку
➡️ Пройти тест и забрать скидку: https://vk.cc/cLkRLn
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🏆 Пройди тест из 10 вопросов, проверь свой уровень знаний и получи скидку на онлайн-курс «Kotlin Backend Developer. Professional» от OTUS!
Если успешно пройдешь тест, сможешь забронировать место в группе по выгодной цене! И еще дарим промокод
🎫 Курс можно приобрести в рассрочку
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 Как в Java сломать equals() и потерять данные в HashMap
Сегодня покажу, как неочевидный баг в
Допустим, у вас есть Entity:
Вроде норм. Но теперь добавим такого юзера в
📌 Почему так происходит?
💡 Совет: если вы используете объект как ключ в мапе или добавляете его в Set, не изменяйте его поля, участвующие в
📌 А как правильно?
- Делайте такие поля
- Или используйте неизменяемые типы (
- Или не используйте такие объекты как ключи вовсе.
Вот безопасный вариант с
👉@BookJava
Сегодня покажу, как неочевидный баг в
equals()/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
👍5
Java. Сортировки
Java. Сортировка пузырьком.
Java. О сортировке выбором.
Java. Быстрая сортировка. Объяснение на пальцах)
Java. Оценка сложности алгоритмов сортировки.
Java. Сортировка слиянием.
Java. Сортировка подсчетом.
Java. Сортировка вставками.
Java. Сортировка расческой. От пузырька до расчески.
👉@BookJava
Java. Сортировка пузырьком.
Java. О сортировке выбором.
Java. Быстрая сортировка. Объяснение на пальцах)
Java. Оценка сложности алгоритмов сортировки.
Java. Сортировка слиянием.
Java. Сортировка подсчетом.
Java. Сортировка вставками.
Java. Сортировка расческой. От пузырька до расчески.
👉@BookJava
👍4
⚡️ Квиз на знание Java
Пройти тестирование — сложно! А ты справишься?
21 вопрос, 30 минут
Проверь себя - пройди квиз и оцени свой уровень навыков, а также свою готовность к обучению на курсе — «Разработчик на Spring Framework» от OTUS.
💻 За 5 месяцев обучения ты освоишь современные возможности Spring, научишься быстро проходить путь от идеи до production-grade, создавать Web-приложения на микросервисной архитектуре и решать высокоуровневые задачи по разработке.
👉 ПРОЙТИ ТЕСТ: https://vk.cc/cLshxc
Если успешно пройдешь тест, сможешь забронировать место в группе по выгодной цене! И еще дарим промокодSPRING5
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Пройти тестирование — сложно! А ты справишься?
21 вопрос, 30 минут
Проверь себя - пройди квиз и оцени свой уровень навыков, а также свою готовность к обучению на курсе — «Разработчик на Spring Framework» от OTUS.
💻 За 5 месяцев обучения ты освоишь современные возможности Spring, научишься быстро проходить путь от идеи до production-grade, создавать Web-приложения на микросервисной архитектуре и решать высокоуровневые задачи по разработке.
👉 ПРОЙТИ ТЕСТ: https://vk.cc/cLshxc
Если успешно пройдешь тест, сможешь забронировать место в группе по выгодной цене! И еще дарим промокод
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👍1👎1
🧠 Знаешь ли ты, что
Да, Spring просто не применяет прокси к
📌 Почему так происходит?
Spring AOP по умолчанию использует динамические прокси (JDK или CGLIB), которые перехватывают внешние вызовы. А вызов
Пример, который НЕ работает:
💡 Как правильно:
1. Сделай метод
2. Или выноси в отдельный бин:
⚠️ Проверь свои сервисы — ты можешь удивиться, сколько транзакций у тебя не работают. Особенно в проектах, где
👉@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
ставят "на всякий случай".👉@BookJava
👍4
🧠 ExecutorService vs Virtual Threads: подводный камень с shutdownNow()
Java 21 принес Virtual Threads (Preview), и всё чаще появляется соблазн запускать их через
📌
💡 Почему?
Метод
Уже запущенные виртуальные потоки продолжают выполняться —
⚠️ Следствие:
Если вы рассчитываете, что
🔧 Что делать?
1. Контролируйте завершение через
2. Стройте явную кооперативную модель отмены — с флагами или
3. Для массовой отмены — храните
👉@BookJava
Java 21 принес Virtual Threads (Preview), и всё чаще появляется соблазн запускать их через
ExecutorService
. Но вот что важно помнить:📌
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
👍4
🧠 Нюанс с
Многие Java-разработчики на автомате пишут что-то вроде:
⚠️ Проблема:
📌 Правильный способ — использовать
💡
🔁 Аналогичная ситуация с
🧪 Мини-памятка:
*
*
👉@BookJava
Optional.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>
).👉@BookJava
👍16
📊 Данные — это топливо цифрового бизнеса. Однако передача данных между системами по-прежнему требует времени, ресурсов и нервов. Kafka Connect меняет правила игры: минимум кода, максимум автоматизации. 🔄
📅 12 мая в 18:00 МСК на открытом вебинаре от OTUS:
— Разберём архитектуру Kafka Connect;
— Запустим коннекторы для БД и файловых систем;
— Научим масштабировать и отлаживать интеграции;
— Покажем, как избежать типовых ошибок.
👤 Спикер: Валентин Шилин — старший программист/аналитик данных в зарубежной компании.
Этот вебинар будет полезен разработчикам, инженерам данных, архитекторам и всем, кто работает с интеграциями. 🌐
Открытый урок проходит в преддверии старта курса «Apache Kafka». Все участники получат скидку на обучение.
🔗 Регистрируйтесь прямо сейчас: https://vk.cc/cLEosU
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
📅 12 мая в 18:00 МСК на открытом вебинаре от OTUS:
— Разберём архитектуру Kafka Connect;
— Запустим коннекторы для БД и файловых систем;
— Научим масштабировать и отлаживать интеграции;
— Покажем, как избежать типовых ошибок.
👤 Спикер: Валентин Шилин — старший программист/аналитик данных в зарубежной компании.
Этот вебинар будет полезен разработчикам, инженерам данных, архитекторам и всем, кто работает с интеграциями. 🌐
Открытый урок проходит в преддверии старта курса «Apache Kafka». Все участники получат скидку на обучение.
🔗 Регистрируйтесь прямо сейчас: https://vk.cc/cLEosU
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠Понимание архитектуры памяти JVM
В этой статье мы углубимся в архитектуру памяти JVM (Java Virtual Machine), исследуя, как она управляет памятью, чтобы эффективно выполнять Java-программы.
🔹 Что такое JVM?
JVM — это абстрактная вычислительная машина, которая позволяет запускать Java-программы, независимо от аппаратного или операционного окружения. Она отвечает за интерпретацию байт-кода Java и выполнение его на хост-машине.
Архитектура памяти JVM
Архитектура памяти JVM делится на несколько компонентов, каждый из которых играет ключевую роль в управлении памятью во время выполнения программы. Основные области памяти JVM:
🔹 1. Heap (Куча)
* Куча — это основная область памяти, используемая для хранения объектов.
* Это самая большая часть памяти и управляется сборщиком мусора (Garbage Collector).
* Объекты, созданные во время выполнения программы, размещаются в куче.
Куча делится на:
* Young Generation (Молодое поколение):
* Хранит недавно созданные объекты.
* Подразделяется на Eden Space и два Survivor Space (S0 и S1).
* Old Generation (Старое поколение):
* Хранит долго живущие объекты, которые пережили несколько сборок мусора в молодом поколении.
🔹 2. Stack (Стек)
* У каждой нити (потока) есть свой собственный стек.
* Хранит фреймы методов, включая локальные переменные, операнды и возвращаемые адреса.
* Память в стеке освобождается, когда метод завершает выполнение.
🔹 3. Method Area (Область методов)
* Используется для хранения метаданных классов, таких как:
* Информация о типах,
* Константы,
* Статические переменные,
* Методы и их байт-код.
* В некоторых реализациях JVM эта область называется Metaspace (начиная с Java 8).
🔹 4. Program Counter (PC Register)
* Каждая нить имеет собственный регистр PC.
* Хранит адрес текущей выполняемой инструкции.
🔹 5. Native Method Stack (Стек нативных методов)
* Используется для выполнения нативных (не-Java) методов, написанных на других языках, таких как C или C++.
Управление памятью и сборка мусора
JVM использует автоматическую сборку мусора для управления кучей. Различные алгоритмы (например, Mark and Sweep, Generational GC) используются для отслеживания и удаления неиспользуемых объектов. Это повышает производительность и предотвращает утечки памяти.
https://thedeveloperstory.com/2025/04/06/understanding-jvm-memory-architecture/
👉@BookJava
В этой статье мы углубимся в архитектуру памяти JVM (Java Virtual Machine), исследуя, как она управляет памятью, чтобы эффективно выполнять Java-программы.
JVM — это абстрактная вычислительная машина, которая позволяет запускать Java-программы, независимо от аппаратного или операционного окружения. Она отвечает за интерпретацию байт-кода Java и выполнение его на хост-машине.
Архитектура памяти JVM
Архитектура памяти JVM делится на несколько компонентов, каждый из которых играет ключевую роль в управлении памятью во время выполнения программы. Основные области памяти JVM:
* Куча — это основная область памяти, используемая для хранения объектов.
* Это самая большая часть памяти и управляется сборщиком мусора (Garbage Collector).
* Объекты, созданные во время выполнения программы, размещаются в куче.
Куча делится на:
* Young Generation (Молодое поколение):
* Хранит недавно созданные объекты.
* Подразделяется на Eden Space и два Survivor Space (S0 и S1).
* Old Generation (Старое поколение):
* Хранит долго живущие объекты, которые пережили несколько сборок мусора в молодом поколении.
* У каждой нити (потока) есть свой собственный стек.
* Хранит фреймы методов, включая локальные переменные, операнды и возвращаемые адреса.
* Память в стеке освобождается, когда метод завершает выполнение.
* Используется для хранения метаданных классов, таких как:
* Информация о типах,
* Константы,
* Статические переменные,
* Методы и их байт-код.
* В некоторых реализациях JVM эта область называется Metaspace (начиная с Java 8).
* Каждая нить имеет собственный регистр PC.
* Хранит адрес текущей выполняемой инструкции.
* Используется для выполнения нативных (не-Java) методов, написанных на других языках, таких как C или C++.
Управление памятью и сборка мусора
JVM использует автоматическую сборку мусора для управления кучей. Различные алгоритмы (например, Mark and Sweep, Generational GC) используются для отслеживания и удаления неиспользуемых объектов. Это повышает производительность и предотвращает утечки памяти.
https://thedeveloperstory.com/2025/04/06/understanding-jvm-memory-architecture/
👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
💡 Не делай этого в
Сейчас расскажу, почему инициализировать важную бизнес-логику в
Типичный пример:
🧨 Проблема:
Если внутри будет ошибка (например, БД недоступна) — приложение может упасть, или что хуже — запуститься в полурабочем состоянии.
📌 Кроме того:
* ❌ Нет контроля над порядком выполнения таких методов;
* ❌ Нельзя легко переиспользовать эту логику (например, вручную перезагрузить кеш);
* ❌ В тестах или dev-среде — такие вызовы часто мешают.
✅ Современный подход — использовать
📌 Альтернатива — аннотация
📦 В Spring Boot это нативный и рекомендованный способ выполнения кода после старта.
🧠 Резюме:
🔹
🔹 Бизнес-логику и I/O — в
👉@BookJava
@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)
.👉@BookJava
👍11😁1
На открытом уроке «Kotlin Multiplatform: лайфхак для Java-разработчиков» от OTUS мы покажем, как с помощью Kotlin Multiplatform (KMP) использовать один и тот же код для различных проектов — от Android и iOS до backend-систем.
Что вас ждёт:
Открытый урок проходит в преддверии старта курса «Kotlin Backend Developer. Professional».
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🤮1
🧠 Spring Boot: как НЕ попасть в ловушку с
Когда используете
📌 Если таких задач несколько или они работают долго — остальные ждут. Это создаёт бутылочное горлышко и приводит к неожиданным задержкам.
💡 Решение: настроить кастомный executor:
Теперь все
⚠️ Не забывайте: если задача критична к ресурсам или зависит от внешних сервисов — добавьте внутреннюю защиту от повторного выполнения, например, с помощью Redis lock или базы.
✅ Подключение пула — must-have для production-проектов, где
👉@BookJava
@Scheduled
и многопоточностьюКогда используете
@Scheduled
для периодических задач в Spring Boot, важно понимать: по умолчанию все задачи выполняются в одном потоке.
@Scheduled(fixedRate = 5000)
public void syncData() {
// долгая операция
}
📌 Если таких задач несколько или они работают долго — остальные ждут. Это создаёт бутылочное горлышко и приводит к неожиданным задержкам.
💡 Решение: настроить кастомный executor:
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // количество параллельных задач
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
registrar.setTaskScheduler(scheduler);
}
}
Теперь все
@Scheduled
- методы будут использовать пул потоков, а не один.⚠️ Не забывайте: если задача критична к ресурсам или зависит от внешних сервисов — добавьте внутреннюю защиту от повторного выполнения, например, с помощью Redis lock или базы.
✅ Подключение пула — must-have для production-проектов, где
@Scheduled
выполняет реальные задачи, а не просто println
.👉@BookJava
👍13
🧠 Spring Boot: правильно логируем
Сейчас покажу простой, но часто упускаемый момент при обработке ошибок в Spring Boot.
Если у вас есть глобальный
❌ Плохо:
✅ Хорошо:
📌
* Сохранять stacktrace для дебага;
* Не терять вложенные причины (
* Работать с лог-агрегаторами (ELK, Grafana, etc).
💡 Если вы используете Slf4j и формат
⚠️ И не забывайте: глобальные хендлеры — это хорошо, но не глушите все ошибки без разбора. Лучше создавать разные
👉@BookJava
@ExceptionHandler
Сейчас покажу простой, но часто упускаемый момент при обработке ошибок в Spring Boot.
Если у вас есть глобальный
@ExceptionHandler
, убедитесь, что вы не теряете stacktrace при логировании.❌ Плохо:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred: {}", ex.getMessage()); // stacktrace теряется!
return ResponseEntity.status(500).body("Error");
}
✅ Хорошо:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred", ex); // stacktrace будет видно в логе
return ResponseEntity.status(500).body("Error");
}
📌
log.error(String, Throwable)
— правильный способ логировать исключения. Это позволяет:* Сохранять stacktrace для дебага;
* Не терять вложенные причины (
getCause()
);* Работать с лог-агрегаторами (ELK, Grafana, etc).
💡 Если вы используете Slf4j и формат
log.error("message: {}", ex.getMessage())
, вы теряете почти всю полезную информацию об ошибке.⚠️ И не забывайте: глобальные хендлеры — это хорошо, но не глушите все ошибки без разбора. Лучше создавать разные
@ExceptionHandler
под каждую категорию исключений.👉@BookJava
👍8