Разбираем CompletableFuture: 7 приёмов для асинхронного Java-кода!
CompletableFuture помогает строить цепочки асинхронных операций без ручного управления Thread. С ним удобно запускать фоновые задачи, преобразовывать результаты, объединять независимые запросы, обрабатывать ошибки и ограничивать время ожидания.
👉 Java Ready | #шпора
CompletableFuture помогает строить цепочки асинхронных операций без ручного управления Thread. С ним удобно запускать фоновые задачи, преобразовывать результаты, объединять независимые запросы, обрабатывать ошибки и ограничивать время ожидания.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤3👍3🤝2
Полезный разбор Java-приложений в Kubernetes!
Автор собирает практики, которые помогают запускать JVM-сервисы в контейнерах предсказуемо и без лишних проблем с ресурсами, healthcheck и завершением процесса.
В статье автор показывает:
• как учитывать память контейнера при настройке JVM
• зачем нужны readiness и liveness probes
• почему graceful shutdown важен для сервисов под нагрузкой
👉 Java Ready | #статья
Автор собирает практики, которые помогают запускать JVM-сервисы в контейнерах предсказуемо и без лишних проблем с ресурсами, healthcheck и завершением процесса.
В статье автор показывает:
• как учитывать память контейнера при настройке JVM
• зачем нужны readiness и liveness probes
• почему graceful shutdown важен для сервисов под нагрузкой
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2
Почему изменяемый ключ в HashMap может сломать поиск?
HashMap ищет значение не просто по содержимому объекта. Сначала он использует hashCode ключа, чтобы выбрать внутреннюю корзину, а потом уже сравнивает ключи через equals.
Представим ключ, где hashCode зависит от поля name:
Если такой объект положить в map, всё выглядит нормально:
Проблема начинается, если после вставки изменить поле, участвующее в equals или hashCode.
Например так:
Теперь объект физически лежит в той корзине, которая была рассчитана для alice. Но при поиске HashMap уже считает хеш для bob и идёт искать в другое место.
Из-за этого даже containsKey может вернуть false:
Это выглядит очень странно, потому что ссылка на объект осталась той же. Но для HashMap важна стабильность equals и hashCode на всё время, пока объект используется как ключ.
Надёжнее делать ключ неизменяемым:
Если нужен обычный класс, поля ключа лучше сделать final и не давать сеттеры. Тогда объект не сможет поменять свой hashCode после добавления в map.
👉 Java Ready | #совет
HashMap ищет значение не просто по содержимому объекта. Сначала он использует hashCode ключа, чтобы выбрать внутреннюю корзину, а потом уже сравнивает ключи через equals.
Представим ключ, где hashCode зависит от поля name:
class UserKey {
String name;
UserKey(String name) {
this.name = name;
}
}Если такой объект положить в map, всё выглядит нормально:
var key = new UserKey("alice");
map.put(key, "admin");Проблема начинается, если после вставки изменить поле, участвующее в equals или hashCode.
Например так:
key.name = "bob";
System.out.println(map.get(key));
Теперь объект физически лежит в той корзине, которая была рассчитана для alice. Но при поиске HashMap уже считает хеш для bob и идёт искать в другое место.
Из-за этого даже containsKey может вернуть false:
System.out.println(map.containsKey(key));
Это выглядит очень странно, потому что ссылка на объект осталась той же. Но для HashMap важна стабильность equals и hashCode на всё время, пока объект используется как ключ.
Надёжнее делать ключ неизменяемым:
record UserKey(String name) {}Если нужен обычный класс, поля ключа лучше сделать final и не давать сеттеры. Тогда объект не сможет поменять свой hashCode после добавления в map.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤3👍3
Шпаргалка по состояниям потока в Java!
Например, NEW означает созданный, но ещё не запущенный поток, RUNNABLE готов к выполнению, WAITING ждёт другого события, а TERMINATED означает завершение работы.
На картинке показан lifecycle потока. Есть переходы между NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING и TERMINATED, а также методы, которые могут переводить поток из одного состояния в другое.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
Например, NEW означает созданный, но ещё не запущенный поток, RUNNABLE готов к выполнению, WAITING ждёт другого события, а TERMINATED означает завершение работы.
На картинке показан lifecycle потока. Есть переходы между NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING и TERMINATED, а также методы, которые могут переводить поток из одного состояния в другое.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤7👍4
Делаем простую очередь задач через BlockingQueue!
Когда один поток создаёт задачи, а другой их обрабатывает, не хочется вручную писать wait, notify и синхронизацию вокруг списка.
Для такого сценария в Java есть BlockingQueue. Она сама умеет ждать элементы и блокировать запись, если очередь ограничена по размеру.
Сначала создадим очередь с лимитом:
Producer может добавлять задачи через put:
Если очередь заполнена, put подождёт свободное место. Это полезно, когда обработчик не успевает за входящим потоком задач.
Consumer забирает элементы через take:
Если очередь пустая, take будет ждать новый элемент. Поэтому не нужен бесконечный цикл с Thread.sleep и ручной проверкой size.
Часто worker запускают в отдельном потоке:
Для остановки можно использовать interrupt или специальную poison pill задачу. Главное, что обмен между потоками остаётся явным и контролируемым.
BlockingQueue хорошо подходит для фоновой обработки писем, логов, webhook-событий, небольших worker-пулов и простых producer-consumer задач.
👉 Java Ready | #практика
Когда один поток создаёт задачи, а другой их обрабатывает, не хочется вручную писать wait, notify и синхронизацию вокруг списка.
Для такого сценария в Java есть BlockingQueue. Она сама умеет ждать элементы и блокировать запись, если очередь ограничена по размеру.
Сначала создадим очередь с лимитом:
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
Producer может добавлять задачи через put:
queue.put("send-email");
queue.put("build-report");Если очередь заполнена, put подождёт свободное место. Это полезно, когда обработчик не успевает за входящим потоком задач.
Consumer забирает элементы через take:
String task = queue.take();
System.out.println(task);
Если очередь пустая, take будет ждать новый элемент. Поэтому не нужен бесконечный цикл с Thread.sleep и ручной проверкой size.
Часто worker запускают в отдельном потоке:
while (!Thread.currentThread().isInterrupted()) {
String task = queue.take();
handle(task);
}Для остановки можно использовать interrupt или специальную poison pill задачу. Главное, что обмен между потоками остаётся явным и контролируемым.
BlockingQueue хорошо подходит для фоновой обработки писем, логов, webhook-событий, небольших worker-пулов и простых producer-consumer задач.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥3
Шпаргалка по JUnit 5!
Например, @Test помечает тестовый метод, @BeforeEach запускает подготовку перед каждым тестом, а assertAll позволяет проверить несколько условий и увидеть все ошибки сразу.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
Например, @Test помечает тестовый метод, @BeforeEach запускает подготовку перед каждым тестом, а assertAll позволяет проверить несколько условий и увидеть все ошибки сразу.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥2
Зачем в Java использовать try-with-resources?
Когда код работает с файлом, соединением или потоком, ресурс нужно закрыть после использования.
Раньше это часто делали через finally:
Если ресурсов несколько, finally быстро разрастается.
В Java для этого есть try-with-resources:
После выхода из блока Java сама вызовет close().
Это работает для любых объектов, которые реализуют AutoCloseable:
Можно открыть несколько ресурсов:
Оба ресурса будут закрыты автоматически, даже если внутри блока возникнет исключение.
Это особенно важно в серверном коде:
Если забывать закрывать такие ресурсы, приложение может постепенно упереться в лимит файлов, соединений или памяти.
👉 Java Ready | #совет
Когда код работает с файлом, соединением или потоком, ресурс нужно закрыть после использования.
Раньше это часто делали через finally:
BufferedReader reader = new BufferedReader(
new FileReader("app.log")
);
try {
System.out.println(reader.readLine());
} finally {
reader.close();
}
Если ресурсов несколько, finally быстро разрастается.
В Java для этого есть try-with-resources:
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
}После выхода из блока Java сама вызовет close().
Это работает для любых объектов, которые реализуют AutoCloseable:
try (Connection connection = dataSource.getConnection()) {
// работа с базой
}Можно открыть несколько ресурсов:
try (
var input = Files.newInputStream(source);
var output = Files.newOutputStream(target)
) {
input.transferTo(output);
}
Оба ресурса будут закрыты автоматически, даже если внутри блока возникнет исключение.
Это особенно важно в серверном коде:
try (var stream = storage.open(fileId)) {
return stream.readAllBytes();
}Если забывать закрывать такие ресурсы, приложение может постепенно упереться в лимит файлов, соединений или памяти.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤4🔥3
Media is too big
VIEW IN TELEGRAM
W3Resource Java Exercises - большая база задач по Java!
На сайте собраны тысячи упражнений по Java с решениями и пояснениями. Есть базовый синтаксис, методы, массивы, строки, коллекции, исключения, потоки, ООП, регулярные выражения и алгоритмы.
Разделы удобно проходить как практику. Можно открыть конкретную тему, решить задачу самостоятельно, сравнить с готовым решением и перейти к похожим упражнениям.
Оставляю ссылочку на W3Resource
👉 Java Ready | #ресурс
На сайте собраны тысячи упражнений по Java с решениями и пояснениями. Есть базовый синтаксис, методы, массивы, строки, коллекции, исключения, потоки, ООП, регулярные выражения и алгоритмы.
Разделы удобно проходить как практику. Можно открыть конкретную тему, решить задачу самостоятельно, сравнить с готовым решением и перейти к похожим упражнениям.
Оставляю ссылочку на W3Resource
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤8🔥5
Собираем маскировщик секретов для логов!
В логах не должны оставаться токены, пароли и API-ключи. Сделаем небольшой sanitizer, который находит опасные поля и заменяет значение на маску.
В этой задаче:
👉 Java Ready | #задача
В логах не должны оставаться токены, пароли и API-ключи. Сделаем небольшой sanitizer, который находит опасные поля и заменяет значение на маску.
В этой задаче:
• описываем шаблон секретного поля
• используем Matcher для замены
• сохраняем имя ключа
• прячем только значение после знака равенства
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3🤝2😁1
Интересный разбор WebFlux и Virtual Threads под нагрузкой!
Автор сравнивает реактивный WebFlux и Spring MVC с виртуальными потоками на микросервисной системе под нагрузкой 2000 RPS.
В статье автор показывает:
• чем event loop отличается от thread-per-request модели
• как WebFlux и Virtual Threads ведут себя при I/O
• какие параметры JVM и Spring влияют на результат
👉 Java Ready | #статья
Автор сравнивает реактивный WebFlux и Spring MVC с виртуальными потоками на микросервисной системе под нагрузкой 2000 RPS.
В статье автор показывает:
• чем event loop отличается от thread-per-request модели
• как WebFlux и Virtual Threads ведут себя при I/O
• какие параметры JVM и Spring влияют на результат
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2🔥2
Шпаргалка по generics в Java!
Например, ? extends T удобно использовать для чтения данных из producer-коллекции, а ? super T подходит для коллекции, в которую нужно безопасно добавлять значения.
На картинке собраны generics, wildcards, PECS, method overloading, recursive generics, covariance и важный нюанс type erasure. Особенно полезно держать под рукой правило Producer Extends Consumer Super.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
Например, ? extends T удобно использовать для чтения данных из producer-коллекции, а ? super T подходит для коллекции, в которую нужно безопасно добавлять значения.
На картинке собраны generics, wildcards, PECS, method overloading, recursive generics, covariance и важный нюанс type erasure. Особенно полезно держать под рукой правило Producer Extends Consumer Super.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3🔥3🤝2
Разбиваем список на батчи в Java!
Когда нужно отправить много записей в API или базу, их часто удобнее обрабатывать пачками. Так проще контролировать нагрузку и размер одного запроса.
Пусть есть список событий:
Размер пачки лучше держать отдельной переменной:
Идём по списку шагом batchSize:
Теперь получаем часть списка:
Эту пачку можно отправить дальше:
Важно что subList возвращает view на исходный список. Если исходный список будут менять параллельно, лучше сделать независимую копию.
Для копии достаточно одной строки:
Такой приём подходит для bulk insert, отправки сообщений, экспорта данных, обработки файлов и любых задач, где один большой список нужно безопасно разложить на управляемые части.
👉 Java Ready | #практика
Когда нужно отправить много записей в API или базу, их часто удобнее обрабатывать пачками. Так проще контролировать нагрузку и размер одного запроса.
Пусть есть список событий:
List events = List.of("a", "b", "c", "d", "e");Размер пачки лучше держать отдельной переменной:
int batchSize = 2;
Идём по списку шагом batchSize:
for (int start = 0; start < events.size(); start += batchSize) {
int end = Math.min(start + batchSize, events.size());
}Теперь получаем часть списка:
List batch = events.subList(start, end);
Эту пачку можно отправить дальше:
sendBatch(batch);
Важно что subList возвращает view на исходный список. Если исходный список будут менять параллельно, лучше сделать независимую копию.
Для копии достаточно одной строки:
List batch = List.copyOf(events.subList(start, end));
Такой приём подходит для bulk insert, отправки сообщений, экспорта данных, обработки файлов и любых задач, где один большой список нужно безопасно разложить на управляемые части.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3🔥2
Media is too big
VIEW IN TELEGRAM
Java Programming MOOC - большой курс Java от University of Helsinki!
На сайте собран большой открытый курс по Java от University of Helsinki. Есть две части, отдельные уроки, упражнения, основы синтаксиса, объекты, коллекции, обработка ошибок, файлы и более продвинутые темы.
Материал удобно проходить по главам. В боковой навигации есть части курса, внутри разделов много объяснений и примеров, поэтому ресурс хорошо подходит как маршрут от базы к уверенной практике.
👉 Java Ready | #ресурс
На сайте собран большой открытый курс по Java от University of Helsinki. Есть две части, отдельные уроки, упражнения, основы синтаксиса, объекты, коллекции, обработка ошибок, файлы и более продвинутые темы.
Материал удобно проходить по главам. В боковой навигации есть части курса, внутри разделов много объяснений и примеров, поэтому ресурс хорошо подходит как маршрут от базы к уверенной практике.
Оставляю ссылочку на Java Programming MOOC
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍5🔥4🤝1
Делаем простой registry обработчиков команд на Java!
Когда команд становится больше, длинная цепочка if/else быстро начинает мешать. Вместо этого можно хранить обработчики в Map.
Опишем интерфейс команды:
Создадим registry:
Добавим пару команд:
Теперь входную строку можно разделить:
Первый элемент — имя команды:
Достаём обработчик:
И запускаем, если команда существует:
Ожидаемый вывод для
Такой подход удобно расширять, новая команда добавляется одной строкой в registry, без переписывания большого switch.
👉 Java Ready | #практика
Когда команд становится больше, длинная цепочка if/else быстро начинает мешать. Вместо этого можно хранить обработчики в Map.
Опишем интерфейс команды:
@FunctionalInterface
interface Command {
void run(String[] args);
}
Создадим registry:
Map<String, Command> commands = new HashMap<>();
Добавим пару команд:
commands.put("hello", args ->
System.out.println("Hello, " + args[0]));
commands.put("sum", args ->
System.out.println(Integer.parseInt(args[0])
+ Integer.parseInt(args[1])));Теперь входную строку можно разделить:
String input = "sum 10 25";
String[] parts = input.split(" ");
Первый элемент — имя команды:
String name = parts[0];
String[] args = Arrays.copyOfRange(parts, 1, parts.length);
Достаём обработчик:
Command command = commands.get(name);
И запускаем, если команда существует:
if (command != null) {
command.run(args);
} else {
System.out.println("Unknown command");
}Ожидаемый вывод для
sum 10 25:35
Такой подход удобно расширять, новая команда добавляется одной строкой в registry, без переписывания большого switch.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤6🔥2😁1