Почему Stream.peek не стоит использовать для бизнес-логики?
peek выглядит удобным способом вставить действие внутрь stream pipeline. Часто туда кладут логирование, метрики или даже изменение объекта.
Например, такой код кажется коротким:
Проблема в том, что peek задуман как инструмент для отладки и наблюдения за элементами, а не как основной шаг бизнес-логики.
Пока нет terminal operation, ничего не выполняется:
В этом примере activate вообще не вызовется, потому что pipeline только описан, но не запущен.
Ещё хуже, когда поведение зависит от terminal operation. Например, count может быть оптимизирован так, что часть промежуточных действий не выполняется ожидаемым способом.
Если нужно изменить объекты явно, лучше использовать обычный цикл:
Если нужно получить новые значения, лучше использовать map:
А peek оставить для временной диагностики:
👉 Java Ready | #совет
peek выглядит удобным способом вставить действие внутрь stream pipeline. Часто туда кладут логирование, метрики или даже изменение объекта.
Например, такой код кажется коротким:
users.stream()
.peek(User::activate)
.toList();
Проблема в том, что peek задуман как инструмент для отладки и наблюдения за элементами, а не как основной шаг бизнес-логики.
Пока нет terminal operation, ничего не выполняется:
users.stream()
.peek(User::activate);
В этом примере activate вообще не вызовется, потому что pipeline только описан, но не запущен.
Ещё хуже, когда поведение зависит от terminal operation. Например, count может быть оптимизирован так, что часть промежуточных действий не выполняется ожидаемым способом.
Если нужно изменить объекты явно, лучше использовать обычный цикл:
for (User user : users) {
user.activate();
}Если нужно получить новые значения, лучше использовать map:
List result = users.stream()
.map(UserDto::from)
.toList();
А peek оставить для временной диагностики:
users.stream()
.peek(user -> log.debug("user {}", user.id()))
.toList();
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥2🤝1
Хорошая статья про обработку ошибок в Kafka!
Автор разбирает ситуацию, когда консьюмер не может обработать сообщение и снова упирается в тот же offset. Дальше показывает, как использовать Dead Letter Queue, retryable и non-retryable ошибки.
В статье автор показывает:
• почему ошибка десериализации может остановить консьюмера
• как настроить DeadLetterPublishingRecoverer в Spring Kafka
• зачем выносить проблемные сообщения в отдельный DLT процессор
👉 Java Ready | #статья
Автор разбирает ситуацию, когда консьюмер не может обработать сообщение и снова упирается в тот же offset. Дальше показывает, как использовать Dead Letter Queue, retryable и non-retryable ошибки.
В статье автор показывает:
• почему ошибка десериализации может остановить консьюмера
• как настроить DeadLetterPublishingRecoverer в Spring Kafka
• зачем выносить проблемные сообщения в отдельный DLT процессор
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2🤝2
Шпаргалка по контейнеризации Java-приложений!
Например, multi-stage build помогает не тащить инструменты сборки в production-образ, а .dockerignore убирает лишние файлы из build context.
На картинке собраны практики для Docker-образов с Java. Есть фиксированные base image теги, минимальный runtime, запуск не от root, обработка сигналов, graceful shutdown, .dockerignore и проверка уязвимостей.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
Например, multi-stage build помогает не тащить инструменты сборки в production-образ, а .dockerignore убирает лишние файлы из build context.
На картинке собраны практики для Docker-образов с Java. Есть фиксированные base image теги, минимальный runtime, запуск не от root, обработка сигналов, graceful shutdown, .dockerignore и проверка уязвимостей.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3🤝1
Собираем поиск по словам на Java!
Нужно быстро находить документы, где встречается слово. Сделаем небольшой inverted index, который хранит для каждого слова набор id документов.
Начнём с хранилища индекса:
Документ можно представить обычной строкой:
Разобьём текст на слова в нижнем регистре:
Теперь добавим каждое слово в индекс:
Поиск становится простым обращением к map:
Такой индекс не заменяет полноценный search engine, но хорошо показывает идею. Мы заранее строим структуру, чтобы потом быстро получать список документов по слову.
👉 Java Ready | #практика
Нужно быстро находить документы, где встречается слово. Сделаем небольшой inverted index, который хранит для каждого слова набор id документов.
Начнём с хранилища индекса:
Map<String, Set<Integer>> index = new HashMap<>();
Документ можно представить обычной строкой:
int id = 7;
String text = "Java streams and maps";
Разобьём текст на слова в нижнем регистре:
String[] words = text
.toLowerCase()
.split("\\W+");
Теперь добавим каждое слово в индекс:
for (String word : words) {
index.computeIfAbsent(word, k -> new HashSet<>())
.add(id);
}Поиск становится простым обращением к map:
Set<Integer> result = index.getOrDefault(
"java",
Set.of()
);
Такой индекс не заменяет полноценный search engine, но хорошо показывает идею. Мы заранее строим структуру, чтобы потом быстро получать список документов по слову.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2🔥2🤝1
Шпаргалка по контейнеризации Java-приложений!
Например, multi-stage build помогает не тащить инструменты сборки в production-образ, а .dockerignore убирает лишние файлы из build context.
На картинке собраны практики для Docker-образов с Java. Есть фиксированные base image теги, минимальный runtime, запуск не от root, обработка сигналов, graceful shutdown, .dockerignore и проверка уязвимостей.
Сохрани, чтобы не потерять!
👉 Java Ready | #совет
Например, multi-stage build помогает не тащить инструменты сборки в production-образ, а .dockerignore убирает лишние файлы из build context.
На картинке собраны практики для Docker-образов с Java. Есть фиксированные base image теги, минимальный runtime, запуск не от root, обработка сигналов, graceful shutdown, .dockerignore и проверка уязвимостей.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2🔥2
Почему BigDecimal.equals может странно работать даже при одинаковом числе?
BigDecimal часто используют для денег, процентов и точных расчётов. Но сравнивать такие значения через equals не всегда то, что ожидается.
Посмотри на пример:
Математически это одно и то же значение. Но equals вернёт false, потому что BigDecimal сравнивает не только число, но и scale.
У этих объектов разный scale:
Для денежных расчётов это может быть неожиданно. Например, значение из базы пришло как 1.00, а значение из расчёта получилось как 1.0.
Если нужно сравнить именно числовое значение, используй compareTo:
compareTo смотрит на математическое значение и не считает scale причиной различия.
А вот в коллекциях equals всё равно важен:
В таком Set будет два элемента, потому что HashSet опирается на equals и hashCode.
Если тебе нужен единый формат, значение можно нормализовать:
Но для денег нормализацию лучше делать осознанно, чтобы не потерять важный формат отображения.
Для числового сравнения BigDecimal используй compareTo, а equals оставляй для случаев, где важен ещё и scale.
👉 Java Ready | #совет
BigDecimal часто используют для денег, процентов и точных расчётов. Но сравнивать такие значения через equals не всегда то, что ожидается.
Посмотри на пример:
var a = new BigDecimal("1.0");
var b = new BigDecimal("1.00");
System.out.println(a.equals(b));Математически это одно и то же значение. Но equals вернёт false, потому что BigDecimal сравнивает не только число, но и scale.
У этих объектов разный scale:
System.out.println(a.scale()); // 1
System.out.println(b.scale()); // 2
Для денежных расчётов это может быть неожиданно. Например, значение из базы пришло как 1.00, а значение из расчёта получилось как 1.0.
Если нужно сравнить именно числовое значение, используй compareTo:
if (a.compareTo(b) == 0) {
System.out.println("same value");
}compareTo смотрит на математическое значение и не считает scale причиной различия.
А вот в коллекциях equals всё равно важен:
Set<BigDecimal> values = new HashSet<>();
values.add(new BigDecimal("1.0"));
values.add(new BigDecimal("1.00"));
В таком Set будет два элемента, потому что HashSet опирается на equals и hashCode.
Если тебе нужен единый формат, значение можно нормализовать:
var normalized = amount.stripTrailingZeros();
Но для денег нормализацию лучше делать осознанно, чтобы не потерять важный формат отображения.
Для числового сравнения BigDecimal используй compareTo, а equals оставляй для случаев, где важен ещё и scale.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥5
Проверяем пересечение временных интервалов на Java!
Нужно понять, конфликтует ли новый слот с уже существующими. Такая задача встречается в календарях, бронированиях, расписаниях задач и системах записи.
В этой задаче:
Вместо набора частных случаев появляется одно универсальное условие.
👉 Java Ready | #задача
Нужно понять, конфликтует ли новый слот с уже существующими. Такая задача встречается в календарях, бронированиях, расписаниях задач и системах записи.
В этой задаче:
• описываем интервал отдельным record
• пишем короткое правило пересечения
• фильтруем занятые слоты через stream
• получаем список конфликтов
Вместо набора частных случаев появляется одно универсальное условие.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3🔥3
Знали, почему Pattern.compile лучше не вызывать в горячем цикле?
В Java регулярные выражения часто используют для валидации, поиска и очистки строк. Рабочий код выглядит так:
Проблема в том, что Pattern.compile создаёт объект шаблона. Если делать это на каждой итерации, программа снова и снова разбирает одно и то же выражение.
Для короткого списка разницы почти не видно. Но в обработке логов, CSV, API-ответов или очередей это превращается в лишнюю нагрузку.
Лучше скомпилировать шаблон один раз:
А внутри цикла только создавать matcher:
Так код показывает, что регулярка постоянная, а не строится заново для каждой строки.
Если шаблон зависит от входных данных, его всё равно можно кешировать по ключу:
Но кешировать стоит только ограниченный набор шаблонов, чтобы не получить бесконечно растущую Map.
👉 Java Ready | #совет
В Java регулярные выражения часто используют для валидации, поиска и очистки строк. Рабочий код выглядит так:
for (String email : emails) {
if (Pattern.compile(".+@.+\\..+").matcher(email).matches()) {
valid.add(email);
}
}Проблема в том, что Pattern.compile создаёт объект шаблона. Если делать это на каждой итерации, программа снова и снова разбирает одно и то же выражение.
Для короткого списка разницы почти не видно. Но в обработке логов, CSV, API-ответов или очередей это превращается в лишнюю нагрузку.
Лучше скомпилировать шаблон один раз:
private static final Pattern EMAIL =
Pattern.compile(".+@.+\\..+");
А внутри цикла только создавать matcher:
for (String email : emails) {
if (EMAIL.matcher(email).matches()) {
valid.add(email);
}
}Так код показывает, что регулярка постоянная, а не строится заново для каждой строки.
Если шаблон зависит от входных данных, его всё равно можно кешировать по ключу:
Pattern pattern = cache.computeIfAbsent(mask, Pattern::compile);
Но кешировать стоит только ограниченный набор шаблонов, чтобы не получить бесконечно растущую Map.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥4🤝1
Java 25 становится проще!
Автор разбирает JEP-512 и изменения, которые позволяют писать небольшие программы с меньшим количеством обязательной обвязки. Вместо класса, static main и ручных импортов можно сосредоточиться на самой задаче, а не на стартовом шаблоне.
В статье разбирают:
• как устроены упрощённые исходники Java 25
• зачем появились неявные импорты и класс IO
• как запускать короткие Java-скрипты из командной строки
👉 Java Ready | #статья
Автор разбирает JEP-512 и изменения, которые позволяют писать небольшие программы с меньшим количеством обязательной обвязки. Вместо класса, static main и ручных импортов можно сосредоточиться на самой задаче, а не на стартовом шаблоне.
В статье разбирают:
• как устроены упрощённые исходники Java 25
• зачем появились неявные импорты и класс IO
• как запускать короткие Java-скрипты из командной строки
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3
Почему Files.lines() нельзя оставлять без try-with-resources?
Files.lines() выглядит как удобный способ быстро получить Stream строк из файла. Но за этим Stream остаётся открытый файловый ресурс.
После count поток может быть исчерпан, но файл не обязан закрыться сразу. В долгоживущем сервисе или цикле обработки это приводит к накоплению открытых дескрипторов.
Правильнее ограничить жизнь Stream блоком try-with-resources.
Java закроет поток даже при исключении внутри обработки. Это особенно полезно для больших логов, CSV-файлов и пакетных задач, где не хочется загружать всё содержимое в память.
Если нужен небольшой файл целиком, можно взять готовый список строк.
readAllLines проще, но читает файл полностью. Files.lines подходит для потоковой обработки, если не забывать закрывать ресурс.
👉 Java Ready | #совет
Files.lines() выглядит как удобный способ быстро получить Stream строк из файла. Но за этим Stream остаётся открытый файловый ресурс.
Stream<String> lines = Files.lines(path);
long count = lines.count();
После count поток может быть исчерпан, но файл не обязан закрыться сразу. В долгоживущем сервисе или цикле обработки это приводит к накоплению открытых дескрипторов.
Правильнее ограничить жизнь Stream блоком try-with-resources.
try (Stream<String> lines = Files.lines(path)) {
long count = lines.count();
}Java закроет поток даже при исключении внутри обработки. Это особенно полезно для больших логов, CSV-файлов и пакетных задач, где не хочется загружать всё содержимое в память.
Если нужен небольшой файл целиком, можно взять готовый список строк.
List<String> lines = Files.readAllLines(path);
readAllLines проще, но читает файл полностью. Files.lines подходит для потоковой обработки, если не забывать закрывать ресурс.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Напоминалка по устройству Java records!
На картинке показано, из каких частей состоит record и что генерирует компилятор.
Например, запись public record Person(String name, int age) создаёт final-поля, канонический конструктор, методы name(), age(), equals() и hashCode() без ручного шаблонного кода.
Сохрани, чтобы не потерять!
👉 Java Ready | #совет
На картинке показано, из каких частей состоит record и что генерирует компилятор.
Например, запись public record Person(String name, int age) создаёт final-поля, канонический конструктор, методы name(), age(), equals() и hashCode() без ручного шаблонного кода.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥4