Проверяем обновления через 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
❤5👍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
Почему нельзя просто проигнорировать 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
Почему equals у массивов в Java не сравнивает содержимое?
В Java массив выглядит как контейнер значений, поэтому легко ожидать, что equals сравнит элементы внутри.
Но массивы не переопределяют equals для содержимого. Обычный equals у массива работает почти как сравнение ссылок.
Например, два массива с одинаковыми числами не будут равны через equals:
Результат будет false, потому что это два разных объекта в памяти.
Для одномерных массивов нужно использовать Arrays.equals:
Теперь Java сравнит элементы по порядку и вернёт true.
С массивами объектов логика такая же:
Но для вложенных массивов Arrays.equals уже недостаточно. Он сравнит внутренние массивы как объекты.
Для двумерных структур нужен deepEquals:
То же самое касается вывода. Обычный toString у массива покажет служебное имя, а не элементы.
Для нормального вывода используют Arrays.toString:
Если массив вложенный, нужен deepToString:
Массивы в Java стоит сравнивать и выводить через Arrays. Иначе код может выглядеть логично, но проверять совсем не то.
👉 Java Ready | #совет
В Java массив выглядит как контейнер значений, поэтому легко ожидать, что equals сравнит элементы внутри.
Но массивы не переопределяют equals для содержимого. Обычный equals у массива работает почти как сравнение ссылок.
Например, два массива с одинаковыми числами не будут равны через equals:
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a.equals(b));Результат будет false, потому что это два разных объекта в памяти.
Для одномерных массивов нужно использовать Arrays.equals:
System.out.println(Arrays.equals(a, b));
Теперь Java сравнит элементы по порядку и вернёт true.
С массивами объектов логика такая же:
String[] left = {"java", "ready"};
String[] right = {"java", "ready"};
System.out.println(Arrays.equals(left, right));Но для вложенных массивов Arrays.equals уже недостаточно. Он сравнит внутренние массивы как объекты.
Для двумерных структур нужен deepEquals:
int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(x, y));То же самое касается вывода. Обычный toString у массива покажет служебное имя, а не элементы.
Для нормального вывода используют Arrays.toString:
System.out.println(Arrays.toString(a));
Если массив вложенный, нужен deepToString:
System.out.println(Arrays.deepToString(x));
Массивы в Java стоит сравнивать и выводить через Arrays. Иначе код может выглядеть логично, но проверять совсем не то.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤3👍3
Шпаргалка по загрузчикам классов в Java!
На картинке показаны Bootstrap, Platform, Application и пользовательский ClassLoader. Для каждого отмечено, какие классы он обычно загружает и как связана их видимость.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
На картинке показаны Bootstrap, Platform, Application и пользовательский ClassLoader. Для каждого отмечено, какие классы он обычно загружает и как связана их видимость.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤3👍3
Знали, что Map.getOrDefault может вызвать загрузку, даже когда ключ уже есть?
Java сначала вычисляет аргументы метода, и только потом вызывает сам метод.
Поэтому дорогой вызов справа выполнится заранее:
Даже если users уже содержит id, loadUser(id) успеет выполниться. Если это запрос к базе, сеть или вычисление, приложение потратит время зря.
Для ленивого вычисления значения подойдёт computeIfAbsent:
Функция загрузки вызывается только тогда, когда ключ отсутствует или связан с null. Возвращённое ненулевое значение добавляется в map и дальше берётся из неё.
Проверь и семантику отсутствующего результата. Если загрузчик вернёт null, запись не появится, и следующий вызов снова попробует загрузить пользователя:
Ещё один нюанс касается конкурентного доступа. Для параллельных потоков выбирай подходящую concurrent map и не помещай в функцию долгую блокирующую работу без понимания гарантий конкретной реализации.
👉 Java Ready | #совет
Java сначала вычисляет аргументы метода, и только потом вызывает сам метод.
Поэтому дорогой вызов справа выполнится заранее:
User user = users.getOrDefault(id, loadUser(id));
Даже если users уже содержит id, loadUser(id) успеет выполниться. Если это запрос к базе, сеть или вычисление, приложение потратит время зря.
Для ленивого вычисления значения подойдёт computeIfAbsent:
User user = users.computeIfAbsent(id, this::loadUser);
Функция загрузки вызывается только тогда, когда ключ отсутствует или связан с null. Возвращённое ненулевое значение добавляется в map и дальше берётся из неё.
Проверь и семантику отсутствующего результата. Если загрузчик вернёт null, запись не появится, и следующий вызов снова попробует загрузить пользователя:
User user = users.computeIfAbsent(id, key -> findUser(key).orElse(null));
Ещё один нюанс касается конкурентного доступа. Для параллельных потоков выбирай подходящую concurrent map и не помещай в функцию долгую блокирующую работу без понимания гарантий конкретной реализации.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3❤2
Проверяем подпись webhook через HMAC в Java!
Webhook нельзя считать доверенным только потому, что он пришёл по HTTPS. Подпись позволяет проверить, что отправитель знает общий секрет и что тело запроса не изменилось по пути.
В этой задаче:
Проверяй подпись до обработки данных и используй формат, который задаёт webhook.
👉 Java Ready | #задача
Webhook нельзя считать доверенным только потому, что он пришёл по HTTPS. Подпись позволяет проверить, что отправитель знает общий секрет и что тело запроса не изменилось по пути.
В этой задаче:
• декодируем секретный ключ
• вычисляем HMAC-SHA256 по исходному телу
• сравниваем результат с подписью запроса
Проверяй подпись до обработки данных и используй формат, который задаёт webhook.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥2
Шпаргалка по выбору типа даты и времени в Java!
Для календарного интервала вроде срока подписки используют Period, а для точной длительности в часах и секундах подходит Duration. Когда важен конкретный момент, выбирают Instant или тип с часовым поясом.
На картинке собрана схема выбора между LocalDate, LocalTime, LocalDateTime, Instant и ZonedDateTime. Она помогает не потерять часовой пояс там, где он влияет на расписание и переходы летнего времени.
Сохрани, чтобы не потерять!
👉 Java Ready | #ресурс
Для календарного интервала вроде срока подписки используют Period, а для точной длительности в часах и секундах подходит Duration. Когда важен конкретный момент, выбирают Instant или тип с часовым поясом.
На картинке собрана схема выбора между LocalDate, LocalTime, LocalDateTime, Instant и ZonedDateTime. Она помогает не потерять часовой пояс там, где он влияет на расписание и переходы летнего времени.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3
Ищем новичков во frontend-разработке и вёрстке сайтов.
Айтилогия запускает бесплатное обучение, где будет:
1. Практика на реальном заказе ценой до 10 000₽.
2. Разбор работ куратором.
3. Задачи от Fullstack-разработчика с 12-летним опытом.
4. Именной сертификат.
И главное — ты почувствуешь уверенность.
Потому что увидишь, что выполнить заказ на frontend-проект тебе по силам.
👉 Приходи на бесплатное обучение и зови с собой друзей
🔥 С 2019 Айтилогия стабильно помогает с обучением, практикой, зарабатывать на фрилансе и проходить собеседования.
Айтилогия запускает бесплатное обучение, где будет:
1. Практика на реальном заказе ценой до 10 000₽.
2. Разбор работ куратором.
3. Задачи от Fullstack-разработчика с 12-летним опытом.
4. Именной сертификат.
И главное — ты почувствуешь уверенность.
Потому что увидишь, что выполнить заказ на frontend-проект тебе по силам.
👉 Приходи на бесплатное обучение и зови с собой друзей
🔥 С 2019 Айтилогия стабильно помогает с обучением, практикой, зарабатывать на фрилансе и проходить собеседования.
Проверяем пересечение бронирований на Java!
В календаре нужно отклонить новую бронь, если выбранное время уже занято. Для проверки достаточно сравнить начало и конец двух интервалов, не перебирая каждую минуту внутри них.
Сделаем модель с полуоткрытым интервалом, где начало включается, а конец уже свободен:
Проверка не даст создать бронь нулевой или отрицательной длины. Условие пересечения учитывает обе границы и не считает соседние интервалы конфликтом.
Подготовим уже занятые промежутки и новый запрос:
Здесь предполагается, что все значения уже приведены к одной временной зоне. Для расписания в нескольких регионах лучше сравнивать Instant, а исходный ZoneId сохранять отдельно.
Проверим весь список и получим понятный результат:
anyMatch завершает проверку, как только найдёт первое пересечение. Если conflict равен false, новую бронь можно добавить, а интервалы, которые соприкасаются ровно по границе, останутся допустимыми.
👉 Java Ready | #практика
В календаре нужно отклонить новую бронь, если выбранное время уже занято. Для проверки достаточно сравнить начало и конец двух интервалов, не перебирая каждую минуту внутри них.
Сделаем модель с полуоткрытым интервалом, где начало включается, а конец уже свободен:
record Booking(LocalDateTime start, LocalDateTime end) {
Booking {
if (!start.isBefore(end)) {
throw new IllegalArgumentException("End must be after start");
}
}
boolean overlaps(Booking other) {
return start.isBefore(other.end())
&& other.start().isBefore(end);
}
}Проверка не даст создать бронь нулевой или отрицательной длины. Условие пересечения учитывает обе границы и не считает соседние интервалы конфликтом.
Подготовим уже занятые промежутки и новый запрос:
List<Booking> existing = List.of(
new Booking(start1, end1),
new Booking(start2, end2)
);
Booking requested = new Booking(requestStart, requestEnd);
Здесь предполагается, что все значения уже приведены к одной временной зоне. Для расписания в нескольких регионах лучше сравнивать Instant, а исходный ZoneId сохранять отдельно.
Проверим весь список и получим понятный результат:
boolean conflict = existing.stream()
.anyMatch(booking -> booking.overlaps(requested));
anyMatch завершает проверку, как только найдёт первое пересечение. Если conflict равен false, новую бронь можно добавить, а интервалы, которые соприкасаются ровно по границе, останутся допустимыми.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤2🔥2
Можно ли запустить Linux из Java, не подключая JNI-библиотеку?
Автор строит небольшой гипервизор на Java 25 и обращается к Linux KVM через Foreign Function & Memory API. Программа открывает /dev/kvm, создаёт виртуальную машину и настраивает память для гостевой системы.
В статье разбирают:
• как FFM описывает нативные функции и структуры KVM
• какие шаги нужны, чтобы загрузить ядро Linux
• как подключить консоль гостевой системы к терминалу Java
В результате внутри процесса Java стартует минимальная Linux-система с BusyBox и интерактивной оболочкой.
👉 Java Ready | #статья
Автор строит небольшой гипервизор на Java 25 и обращается к Linux KVM через Foreign Function & Memory API. Программа открывает /dev/kvm, создаёт виртуальную машину и настраивает память для гостевой системы.
В статье разбирают:
• как FFM описывает нативные функции и структуры KVM
• какие шаги нужны, чтобы загрузить ядро Linux
• как подключить консоль гостевой системы к терминалу Java
В результате внутри процесса Java стартует минимальная Linux-система с BusyBox и интерактивной оболочкой.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3
Сверстал сайт → заработал 20 000₽.
Просто вдумайся:
Кто-то каждый день тратит по 3–4 часа на дорогу за скромную зарплату.
Пробки, маршрутки, час-пик — один и тот же маршрут годами.
А кто-то за это же время…
собирает бизнесу сайт или апку со своего ноутбука, без привязки к месту и графику, и получает европейскую зарплату.
Без сложных задач.
Без многолетнего обучения.
Без дорогого ноутбука.
Задача проста:
1. Берёшь информацию клиента
2. Прогоняешь через GPT
3. Верстаешь сайт по блокам
4. Грузишь на хостинг
5. Сдаёшь работу и получаешь деньги.
Новички без опыта обычно выходят на 50–80 тыс ₽ в месяц,
и за год легко вырастают до 150 тыс ₽ и выше.
Работаешь в удобное тебе время, поэтому легко совмещать с текущей работой, учёбой, путешествием по миру…
👉 Пройди бесплатное обучение, и ты поймёшь, что это не просто слова.
Начинаем уже завтра.
Реклама. ИП Чернова О. А., ИНН:771399721044
Просто вдумайся:
Кто-то каждый день тратит по 3–4 часа на дорогу за скромную зарплату.
Пробки, маршрутки, час-пик — один и тот же маршрут годами.
А кто-то за это же время…
собирает бизнесу сайт или апку со своего ноутбука, без привязки к месту и графику, и получает европейскую зарплату.
Без сложных задач.
Без многолетнего обучения.
Без дорогого ноутбука.
Задача проста:
1. Берёшь информацию клиента
2. Прогоняешь через GPT
3. Верстаешь сайт по блокам
4. Грузишь на хостинг
5. Сдаёшь работу и получаешь деньги.
Новички без опыта обычно выходят на 50–80 тыс ₽ в месяц,
и за год легко вырастают до 150 тыс ₽ и выше.
Работаешь в удобное тебе время, поэтому легко совмещать с текущей работой, учёбой, путешествием по миру…
👉 Пройди бесплатное обучение, и ты поймёшь, что это не просто слова.
Начинаем уже завтра.
Реклама. ИП Чернова О. А., ИНН:771399721044
😁6
Публикуем готовый отчёт без частично записанного файла!
Допустим, другая программа постоянно проверяет папку с отчётами. Если записывать сразу в итоговый файл, читатель может открыть его посреди записи и увидеть только половину содержимого. Подготовим данные во временном файле, а затем опубликуем готовый результат одним перемещением.
Создадим временный файл прямо в целевой папке. Это важно для атомарного перемещения, которое обычно возможно только внутри одной файловой системы:
Итоговое имя сделаем уникальным для этой публикации. Здесь предполагается, что содержимое уже находится в строке report:
Если запись не удалась, временный файл нужно убрать. Поместим запись и перенос в try, а очистку в finally:
При успешном переносе временного пути уже нет, поэтому deleteIfExists ничего не удалит. Если операция сорвалась, он уберёт незавершённый файл. Другой процесс увидит итоговое имя только после перемещения, а не во время записи.
👉 Java Ready | #практика
Допустим, другая программа постоянно проверяет папку с отчётами. Если записывать сразу в итоговый файл, читатель может открыть его посреди записи и увидеть только половину содержимого. Подготовим данные во временном файле, а затем опубликуем готовый результат одним перемещением.
Создадим временный файл прямо в целевой папке. Это важно для атомарного перемещения, которое обычно возможно только внутри одной файловой системы:
Path dir = Path.of("reports");
Files.createDirectories(dir);
Path temp = Files.createTempFile(dir, ".report-", ".tmp");Итоговое имя сделаем уникальным для этой публикации. Здесь предполагается, что содержимое уже находится в строке report:
Path target = dir.resolve(UUID.randomUUID() + ".txt");
Если запись не удалась, временный файл нужно убрать. Поместим запись и перенос в try, а очистку в finally:
try {
Files.writeString(temp, report, StandardCharsets.UTF_8);
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(temp);
}При успешном переносе временного пути уже нет, поэтому deleteIfExists ничего не удалит. Если операция сорвалась, он уберёт незавершённый файл. Другой процесс увидит итоговое имя только после перемещения, а не во время записи.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥4