Находим три самых популярных товара!
Есть поток покупок с повторяющимися названиями. Построим небольшую сводку, которая считает частоты и показывает лидеров.
В этой задаче:
Когда данных станет очень много, вместо полной сортировки можно оставить только три записи в маленькой куче.
👉 Java Ready | #задача
Есть поток покупок с повторяющимися названиями. Построим небольшую сводку, которая считает частоты и показывает лидеров.
В этой задаче:
• собираем счётчики через Map.merge
• сортируем товары по частоте
• оставляем топ-3 и печатаем результат
Когда данных станет очень много, вместо полной сортировки можно оставить только три записи в маленькой куче.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤3👍1
Почему после Stream.toList() нельзя добавить элемент?
При работе со Stream часто нужно собрать результаты обратно в список. Короткая запись выглядит естественно:
Но у Stream.toList() есть важное свойство, он возвращает немодифицируемый список. Такой код не добавит третье имя:
Вместо изменения списка программа получит UnsupportedOperationException. То же произойдёт при remove или set.
Если нужен готовый результат только для чтения, это как раз удобно:
Вызывающий код не сможет случайно поменять состав labels. При этом сами элементы могут оставаться изменяемыми, не модифицируемость списка не делает глубоко неизменяемыми объекты внутри него.
Если список нужно дополнять после сбора, задайте изменяемую коллекцию явно:
Теперь add и remove доступны. Есть и вариант обернуть готовый результат:
Выбирайте способ сбора по тому, что будет происходить со списком дальше. Для чтения подойдёт toList(), для дальнейших изменений лучше явно создать ArrayList.
👉 Java Ready | #совет
При работе со Stream часто нужно собрать результаты обратно в список. Короткая запись выглядит естественно:
var names = Stream.of("Ana", "Bob").toList();Но у Stream.toList() есть важное свойство, он возвращает немодифицируемый список. Такой код не добавит третье имя:
names.add("Cyd");Вместо изменения списка программа получит UnsupportedOperationException. То же произойдёт при remove или set.
Если нужен готовый результат только для чтения, это как раз удобно:
List<String> labels = users.stream()
.map(User::name)
.toList();
Вызывающий код не сможет случайно поменять состав labels. При этом сами элементы могут оставаться изменяемыми, не модифицируемость списка не делает глубоко неизменяемыми объекты внутри него.
Если список нужно дополнять после сбора, задайте изменяемую коллекцию явно:
var names = Stream.of("Ana", "Bob")
.collect(Collectors.toCollection(ArrayList::new));Теперь add и remove доступны. Есть и вариант обернуть готовый результат:
var names = new ArrayList<>(stream.toList());
Выбирайте способ сбора по тому, что будет происходить со списком дальше. Для чтения подойдёт toList(), для дальнейших изменений лучше явно создать ArrayList.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥4
Безопасная настройка retry для всей системы!
В статье Java-разработчик делится опытом внедрения политики повторных попыток в проекте с Kafka и цепочкой микросервисов. Одной аннотации недостаточно, когда результат операции может оказаться неизвестным, а сообщение пройти обработку дважды.
Автор разбирает:
• какие ошибки имеет смысл повторять, а какие нет
• чем отличаются блокирующие и неблокирующие retry у Kafka consumer
• как повторы на нескольких уровнях создают лишнюю нагрузку
👉 Java Ready | #статья
В статье Java-разработчик делится опытом внедрения политики повторных попыток в проекте с Kafka и цепочкой микросервисов. Одной аннотации недостаточно, когда результат операции может оказаться неизвестным, а сообщение пройти обработку дважды.
Автор разбирает:
• какие ошибки имеет смысл повторять, а какие нет
• чем отличаются блокирующие и неблокирующие retry у Kafka consumer
• как повторы на нескольких уровнях создают лишнюю нагрузку
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Почему split(".") в Java не делит строку по точке?
На первый взгляд кажется, что достаточно передать в split нужный символ:
Но split принимает регулярное выражение. Точка в regex означает любой символ, поэтому такой вызов не ищет буквальную точку между app и log.
Для настоящей точки её нужно экранировать в регулярном выражении. В Java-строке обратный слеш тоже экранируется, поэтому в исходнике получается два слеша:
Теперь результатом будут app и log.
Та же ловушка есть у других специальных символов regex. Например, вертикальная черта обозначает альтернативу. Когда разделитель приходит из переменной, удобнее не вспоминать все правила экранирования:
Pattern.quote превращает текст разделителя в буквальный шаблон. Если пользователь выберет точку, плюс или квадратную скобку, смысл разделения не изменится.
Есть ещё нюанс в конце строки. По умолчанию split убирает пустые элементы справа:
Вы увидите только a и b. Если формат данных считает завершающее пустое поле значимым, передайте отрицательный limit:
Здесь будет три элемента, последний из них пустая строка.
👉 Java Ready | #совет
На первый взгляд кажется, что достаточно передать в split нужный символ:
String[] parts = "app.log".split(".");Но split принимает регулярное выражение. Точка в regex означает любой символ, поэтому такой вызов не ищет буквальную точку между app и log.
Для настоящей точки её нужно экранировать в регулярном выражении. В Java-строке обратный слеш тоже экранируется, поэтому в исходнике получается два слеша:
String[] parts = "app.log".split("\\.");
System.out.println(Arrays.toString(parts));Теперь результатом будут app и log.
Та же ловушка есть у других специальных символов regex. Например, вертикальная черта обозначает альтернативу. Когда разделитель приходит из переменной, удобнее не вспоминать все правила экранирования:
String separator = "|";
String[] fields = line.split(Pattern.quote(separator));
Pattern.quote превращает текст разделителя в буквальный шаблон. Если пользователь выберет точку, плюс или квадратную скобку, смысл разделения не изменится.
Есть ещё нюанс в конце строки. По умолчанию split убирает пустые элементы справа:
System.out.println(Arrays.toString("a,b,".split(",")));Вы увидите только a и b. Если формат данных считает завершающее пустое поле значимым, передайте отрицательный limit:
String[] fields = "a,b,".split(",", -1);Здесь будет три элемента, последний из них пустая строка.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥4
Почему String.format может заменить точку на запятую?
Представим, что программа сохраняет числовое значение в текстовый файл. Хочется получить 12.50, поэтому пишем:
Но результат без явной локали зависит от настроек среды, в которой запущена JVM. На одной машине это будет 12.50, на другой 12,50. Для интерфейса пользователя локальные правила полезны, а для машинного формата такой сюрприз может испортить импорт.
Разницу легко показать без изменения настроек компьютера:
Здесь получится 12,50. Если строку должен читать другой сервис с точкой в качестве разделителя, укажите нейтральную локаль явно:
Теперь получится 12.50 независимо от локали пользователя. Это важно для логов с фиксированным форматом.
Для текста, который читает человек, наоборот, можно передать нужную ему локаль:
Так отображение будет привычным для пользователя. Но если вы записываете структурированные данные, лучше использовать подходящий формат и библиотеку, например JSON-сериализатор, а не собирать документ через String.format.
👉 Java Ready | #совет
Представим, что программа сохраняет числовое значение в текстовый файл. Хочется получить 12.50, поэтому пишем:
String value = String.format("%.2f", 12.5);Но результат без явной локали зависит от настроек среды, в которой запущена JVM. На одной машине это будет 12.50, на другой 12,50. Для интерфейса пользователя локальные правила полезны, а для машинного формата такой сюрприз может испортить импорт.
Разницу легко показать без изменения настроек компьютера:
String de = String.format(Locale.GERMANY, "%.2f", 12.5);
Здесь получится 12,50. Если строку должен читать другой сервис с точкой в качестве разделителя, укажите нейтральную локаль явно:
String stable = String.format(Locale.ROOT, "%.2f", 12.5);
Теперь получится 12.50 независимо от локали пользователя. Это важно для логов с фиксированным форматом.
Для текста, который читает человек, наоборот, можно передать нужную ему локаль:
String label = String.format(userLocale, "%.2f", 12.5);
Так отображение будет привычным для пользователя. Но если вы записываете структурированные данные, лучше использовать подходящий формат и библиотеку, например JSON-сериализатор, а не собирать документ через String.format.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3
Проверяем обновления через ETag в Java!
Когда приложение часто запрашивает один ресурс, не обязательно каждый раз скачивать его целиком. Сохраним тело ответа и метку ETag, а следующий запрос сделаем условным.
В этой задаче:
Так можно сократить передачу неизменившихся данных. Для полноценного кеша дополнительно понадобятся правила хранения и отдельные записи для разных URL.
👉 Java Ready | #задача
Когда приложение часто запрашивает один ресурс, не обязательно каждый раз скачивать его целиком. Сохраним тело ответа и метку ETag, а следующий запрос сделаем условным.
В этой задаче:
• добавляем If-None-Match
• используем кеш при ответе 304
• обновляем данные и ETag при ответе 200
Так можно сократить передачу неизменившихся данных. Для полноценного кеша дополнительно понадобятся правила хранения и отдельные записи для разных URL.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥2
Делаем простой CSV exporter на Java!
Иногда нужно быстро выгрузить данные в CSV. Например, список пользователей, отчёт или результат диагностики.
Главная ошибка в таких задачах состоит в том, что строки часто просто склеивают через запятую. Это ломается, если внутри значения уже есть запятая, кавычка или перенос строки.
Начнём с модели:
Данные могут выглядеть так:
Теперь сделаем функцию экранирования. Если значение содержит запятую, кавычку или перенос строки, оборачиваем его в кавычки:
Для обычных значений функция вернёт строку как есть. Для Bob, Jr. получится корректное CSV-значение в кавычках.
Соберём строку пользователя:
Теперь можно собрать весь файл. Сначала добавим заголовок:
После этого добавим строки данных:
И запишем результат в файл:
На выходе получится корректный CSV:
Такую заготовку можно использовать для небольших экспортов без внешних библиотек. Если появляются вложенные структуры, разные форматы дат, большие файлы или сложные правила, лучше взять полноценную CSV-библиотеку.
👉 Java Ready | #практика
Иногда нужно быстро выгрузить данные в CSV. Например, список пользователей, отчёт или результат диагностики.
Главная ошибка в таких задачах состоит в том, что строки часто просто склеивают через запятую. Это ломается, если внутри значения уже есть запятая, кавычка или перенос строки.
Начнём с модели:
record User(int id, String name, String email) {}Данные могут выглядеть так:
var users = List.of(
new User(1, "Alice", "alice@mail.test"),
new User(2, "Bob, Jr.", "bob@mail.test")
);
Теперь сделаем функцию экранирования. Если значение содержит запятую, кавычку или перенос строки, оборачиваем его в кавычки:
static String csv(String value) {
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}Для обычных значений функция вернёт строку как есть. Для Bob, Jr. получится корректное CSV-значение в кавычках.
Соберём строку пользователя:
static String row(User user) {
return String.join(",",
String.valueOf(user.id()),
csv(user.name()),
csv(user.email())
);
}Теперь можно собрать весь файл. Сначала добавим заголовок:
var lines = new ArrayList<String>();
lines.add("id,name,email");
После этого добавим строки данных:
for (User user : users) {
lines.add(row(user));
}И запишем результат в файл:
Files.write(
Path.of("users.csv"),
lines,
StandardCharsets.UTF_8
);
На выходе получится корректный CSV:
id,name,email
1,Alice,alice@mail.test
2,"Bob, Jr.",bob@mail.test
Такую заготовку можно использовать для небольших экспортов без внешних библиотек. Если появляются вложенные структуры, разные форматы дат, большие файлы или сложные правила, лучше взять полноценную CSV-библиотеку.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3🔥3
Как поддерживать один плагин для Paper и Velocity?
Paper запускает игровой сервер, а Velocity распределяет подключения игроков между серверами.
В статье разбирают:
• как вынести общую логику в отдельный модуль
• зачем платформам нужны собственные адаптеры
• как собрать варианты плагина через Gradle
Общее ядро не зависит от API Paper и Velocity, поэтому одинаковые функции не приходится поддерживать в двух копиях.
👉 Java Ready | #статья
Paper запускает игровой сервер, а Velocity распределяет подключения игроков между серверами.
В статье разбирают:
• как вынести общую логику в отдельный модуль
• зачем платформам нужны собственные адаптеры
• как собрать варианты плагина через Gradle
Общее ядро не зависит от API Paper и Velocity, поэтому одинаковые функции не приходится поддерживать в двух копиях.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤2👍2
Собрала чат-бота за вечер → заработала 10 000₽.
Просто представь, кто-то стоит в очереди на маршрутку в 8 утра чтобы успеть на “любимую” работу.
А Алёна в тепле и уюте…
собирает бизнесу чат-ботов со своего ноутбука. Без привязки к месту и графику получает европейскую зарплату.
Без сложных задач.
Без многолетнего обучения.
Без дорогого ноутбука.
Работа проста:
Берёшь клиента → Собираешь бота по шаблону → Наставник всё проверяет → Сдаёшь работу и получаешь деньги.
Новички без опыта обычно выходят на 50–80 тыс ₽ в месяц,
и за год легко вырастают до 200 тыс ₽ и выше.
Работаешь в удобное тебе время, поэтому легко совмещать с текущей работой, учёбой, декретом…
👉 Пройди бесплатное обучение, и ты поймёшь, что это не просто слова
Бесплатных мест осталось 9.
Просто представь, кто-то стоит в очереди на маршрутку в 8 утра чтобы успеть на “любимую” работу.
А Алёна в тепле и уюте…
собирает бизнесу чат-ботов со своего ноутбука. Без привязки к месту и графику получает европейскую зарплату.
Без сложных задач.
Без многолетнего обучения.
Без дорогого ноутбука.
Работа проста:
Берёшь клиента → Собираешь бота по шаблону → Наставник всё проверяет → Сдаёшь работу и получаешь деньги.
Новички без опыта обычно выходят на 50–80 тыс ₽ в месяц,
и за год легко вырастают до 200 тыс ₽ и выше.
Работаешь в удобное тебе время, поэтому легко совмещать с текущей работой, учёбой, декретом…
👉 Пройди бесплатное обучение, и ты поймёшь, что это не просто слова
Бесплатных мест осталось 9.
👎8
Почему нельзя просто проигнорировать InterruptedException?
Поток может ждать завершения задачи, блокировки или таймера. Другой поток вправе попросить его остановиться через interrupt. Например, worker делает паузу между проверками:
Если вызвать interrupt во время sleep, метод выбросит InterruptedException. При этом флаг прерывания будет очищен. Это важный момент: после перехвата исключения простая проверка isInterrupted уже не увидит запрос на остановку.
Такой обработчик теряет сигнал:
Программа продолжит работу, будто отмены не было. Это особенно неприятно в пуле потоков и при остановке сервиса, вызывающий код считает, что попросил завершить задачу, а она выполняется дальше.
Если метод может передать исключение выше, оставь его в сигнатуре:
Тогда решение об остановке примет вызывающий уровень. А если исключение приходится перехватить внутри Runnable или другого метода без throws, восстанови флаг и заверши работу:
Восстановленный флаг увидит внешний код, который проверяет состояние потока.
👉 Java Ready | #совет
Поток может ждать завершения задачи, блокировки или таймера. Другой поток вправе попросить его остановиться через interrupt. Например, worker делает паузу между проверками:
while (!Thread.currentThread().isInterrupted()) {
processNext();
Thread.sleep(1000);
}Если вызвать interrupt во время sleep, метод выбросит InterruptedException. При этом флаг прерывания будет очищен. Это важный момент: после перехвата исключения простая проверка isInterrupted уже не увидит запрос на остановку.
Такой обработчик теряет сигнал:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("continue");
}Программа продолжит работу, будто отмены не было. Это особенно неприятно в пуле потоков и при остановке сервиса, вызывающий код считает, что попросил завершить задачу, а она выполняется дальше.
Если метод может передать исключение выше, оставь его в сигнатуре:
void pause() throws InterruptedException {
Thread.sleep(1000);
}Тогда решение об остановке примет вызывающий уровень. А если исключение приходится перехватить внутри Runnable или другого метода без throws, восстанови флаг и заверши работу:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}Восстановленный флаг увидит внешний код, который проверяет состояние потока.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥3❤2