Уменьшаем время выборки из БД с 2–3 секунд до ~100 мс:
На первый взгляд запрос нормальный?
ДА, вроде ок.
Но:
- это OFFSET-пагинация
- и это прям ПЛОХО
- БД вытягивает 10020 строк и выкидывает 10000, чтобы показать 20, лол
- чем больше OFFSET, тем больше нагрузка на БД
- со временем запрос начинает занимать больше 2 секунд, пока растет объем данных
Решение:
KEYSET (seek) пагинация: добавляем условие вида created_at < last_seen_timestamp
- так БД может сразу прыгнуть по индексу
- по сути это "дай следующие 20 записей после этого timestamp", где timestamp используется как ключ
- время реально падает с секунд до примерно 100–200 мс
Что если timestamp не уникален, есть дубли:
Добавляем tie-breaker: (created_at, id) и в WHERE, и в ORDER BY:
Так пагинация остается быстрой и детерминированной, даже при одинаковых created_at.
👉 Java Portal
SELECT * FROM transactions
WHERE user_id = 40
ORDER BY created_at DESC
LIMIT 20 OFFSET 10000;
На первый взгляд запрос нормальный?
ДА, вроде ок.
Но:
- это OFFSET-пагинация
- и это прям ПЛОХО
- БД вытягивает 10020 строк и выкидывает 10000, чтобы показать 20, лол
- чем больше OFFSET, тем больше нагрузка на БД
- со временем запрос начинает занимать больше 2 секунд, пока растет объем данных
Решение:
KEYSET (seek) пагинация: добавляем условие вида created_at < last_seen_timestamp
SELECT * FROM transactions
WHERE user_id = 40
AND created_at < '2024-05-01 10:00:00'
ORDER BY created_at DESC
LIMIT 20;
- так БД может сразу прыгнуть по индексу
- по сути это "дай следующие 20 записей после этого timestamp", где timestamp используется как ключ
- время реально падает с секунд до примерно 100–200 мс
Что если timestamp не уникален, есть дубли:
Добавляем tie-breaker: (created_at, id) и в WHERE, и в ORDER BY:
WHERE (created_at, id) < ('2024-05-01 10:00:00', 98765)
ORDER BY created_at DESC, id DESCТак пагинация остается быстрой и детерминированной, даже при одинаковых created_at.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥5❤4
Одна ключевая идея в Spring Boot, которая в 2025 отличает синьоров от staff/principal инженеров:
Большинство разработчиков думают, что Spring Boot это просто
Инженеры уровня staff и выше понимают, что настоящий переломный момент это относиться к потокам как к детали реализации, а не к ресурсу.
Virtual Threads (Project Loom) + Spring Boot 3 позволяют блокирующему коду масштабироваться лучше, чем это когда-либо делал реактивный стек.
Теперь не нужно выбирать упрощение или производительность. Можно иметь оба варианта сразу.
Один такой сдвиг в мышлении может поднять пропускную способность системы в 10 раз и вдвое снизить сложность.
👉 Java Portal
Большинство разработчиков думают, что Spring Boot это просто
@RestController + JPA.Инженеры уровня staff и выше понимают, что настоящий переломный момент это относиться к потокам как к детали реализации, а не к ресурсу.
Virtual Threads (Project Loom) + Spring Boot 3 позволяют блокирующему коду масштабироваться лучше, чем это когда-либо делал реактивный стек.
Теперь не нужно выбирать упрощение или производительность. Можно иметь оба варианта сразу.
Один такой сдвиг в мышлении может поднять пропускную способность системы в 10 раз и вдвое снизить сложность.
@RestController
@RequestMapping("/api/orders")
class OrderController {
@Autowired JdbcTemplate jdbc; // Оставляй свой старый добрый блокирующий JDBC
@Autowired RestTemplate rest; // Оставляй привычный блокирующий RestTemplate
@Autowired EntityManager em; // Оставляй JPA/Hibernate в том виде, как привык
@GetMapping("/{id}")
public Order get(@PathVariable Long id) {
// 100% блокирующий код — читает БД, вызывает 3 downstream HTTP-сервиса
// Но благодаря виртуальным потокам один этот endpoint
// спокойно выдерживает 50k+ RPS на 2 vCPU при < 50 ms p99
var order = jdbc.queryForObject("SELECT * FROM orders WHERE id=?", Order.class, id);
var payment = rest.getForObject("http://payment/{id}", Payment.class, id);
var shipping = rest.getForObject("http://shipping/{id}", Shipping.class, id);
return order.withPayment(payment).withShipping(shipping);
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3🤣3
Приглашаем на ЮMoneyDay — бесплатную онлайн-конференцию про финтех и IT 🔥
На протяжении двух дней будем общаться с разработчиками, инженерами, тестировщиками, продактами, дизайнерами и другими специалистами из ЮMoney. Они расскажут про свой опыт работы в большом финансовом продукте, поделятся лайфхаками и секретами.
Будут доклады по 16 направлениям:
🟣 Будущее финтеха
🟣 Бэкенд
🟣 Фронтенд
🟣 Тестирование
🟣 Python
🟣 Менеджмент проектов
🟣 Менеджмент продуктов
🟣 Системный анализ
🟣 SQL
🟣 UX
🟣 ИИ
🟣 Архитектура IT-решений
🟣 Внутренние системы
🟣 Мобильная разработка
🟣 Инфраструктура
🟣 О компании
Встречаемся онлайн 5 и 6 декабря в 11:00 мск. Чтобы участвовать, зарегистрируйтесь на сайте конференции✅
На протяжении двух дней будем общаться с разработчиками, инженерами, тестировщиками, продактами, дизайнерами и другими специалистами из ЮMoney. Они расскажут про свой опыт работы в большом финансовом продукте, поделятся лайфхаками и секретами.
Будут доклады по 16 направлениям:
Встречаемся онлайн 5 и 6 декабря в 11:00 мск. Чтобы участвовать, зарегистрируйтесь на сайте конференции
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1🔥1
Spring Modulith добавили поддержку Application-Module Aware миграций в Flyway.
Посмотреть, как это работает, можно тут👇
https://github.com/sivaprasadreddy/spring-modular-monolith
👉 Java Portal
Посмотреть, как это работает, можно тут
https://github.com/sivaprasadreddy/spring-modular-monolith
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🤔1
Совет по Spring Boot
Когда пишешь REST API на Spring Boot, обычно используют префикс
Чтобы не лепить
👉 Java Portal
Когда пишешь REST API на Spring Boot, обычно используют префикс
/apiЧтобы не лепить
@RequestMapping("/api") в каждом контроллере, можно настроить это один раз вот так:@Configuration
class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
aClass -> aClass.getPackage().getName().startsWith("com.sivalabs.bookstore"));
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤4😁1
Какой будет вывод и почему?
Если ты не можешь ответить на это, значит ты пока не готов к интервью.
👉 Java Portal
class Person {
String name;
}
public class Test {
static void modify(Person obj) {
obj.name = "Rahul";
obj = new Person();
obj.name = "Amit";
}
public static void main(String[] args) {
Person p = new Person();
p.name = "Sumit";
modify(p);
System.out.println(p.name);
}
}Если ты не можешь ответить на это, значит ты пока не готов к интервью.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍5
Можно использовать асинхронный логгинг в Spring Boot, настроив logback (logback-spring.xml).
Лог-сообщения отправляются в очередь и обрабатываются отдельным фоновым потоком.
Это уменьшает задержки, связанные с вводом-выводом.
Положи файл конфигурации logback в папку resources:
Вот пример:
👉 Java Portal
Лог-сообщения отправляются в очередь и обрабатываются отдельным фоновым потоком.
Это уменьшает задержки, связанные с вводом-выводом.
Положи файл конфигурации logback в папку resources:
src/main/resources/logback-spring.xml
Вот пример:
<configuration>
<!-- Console appender, обёрнутый в асинхронный -->
<appender name="ASYNC_CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="CONSOLE" />
<queueSize>5000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_CONSOLE" />
</root>
</configuration>
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2
Media is too big
VIEW IN TELEGRAM
Разработчик выложил проект ShadowStream, систему отслеживания изменений в базе данных (CDC), построенную на PostgreSQL logical replication.
Цель проекта — ловить любые изменения в реальном времени и передавать их в потоковую инфраструктуру.
Как это работает:
- изменения в базе (INSERT, UPDATE, DELETE) сразу перехватываются через logical replication
- события сериализуются в Protobuf и отправляются в Redis Streams для быстрого доступа
- параллельно те же данные архивируются в Kafka для надежного хранения
- Kafka использует grouped consumer'ов: два обработчика работают параллельно, плюс резервная группа с отдельным offset
- поверх всего в Django Admin добавлена визуализация gRPC-вызовов
Исходники открыты на GitHub
Проект может пригодиться тем, кто работает с потоковой обработкой данных, аналитикой, репликацией или интеграцией микросервисов.🙂
👉 Java Portal
Цель проекта — ловить любые изменения в реальном времени и передавать их в потоковую инфраструктуру.
Как это работает:
- изменения в базе (INSERT, UPDATE, DELETE) сразу перехватываются через logical replication
- события сериализуются в Protobuf и отправляются в Redis Streams для быстрого доступа
- параллельно те же данные архивируются в Kafka для надежного хранения
- Kafka использует grouped consumer'ов: два обработчика работают параллельно, плюс резервная группа с отдельным offset
- поверх всего в Django Admin добавлена визуализация gRPC-вызовов
Исходники открыты на GitHub
Проект может пригодиться тем, кто работает с потоковой обработкой данных, аналитикой, репликацией или интеграцией микросервисов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤4
Java даёт много вариантов по части синтаксиса, но функциональные интерфейсы — одна из самых аккуратных и приятных фишек языка ☕️
Сегодня разберём 4 штуки, которые встречаются чаще всего. Если поймёшь их, писать код станет современнее, местами чище
Идея простая👇
Функциональный интерфейс — это интерфейс с одним абстрактным методом. Благодаря этому его можно реализовать через лямбды.
В Java их много, но вот четыре, которые ты будешь видеть постоянно:
👉 Consumer — делает что-то
Consumer принимает значение и ничего не возвращает.
Отлично подходит для побочных эффектов: логирование, вывод в консоль, сохранение, отправка и так далее.
Проще говоря:
"получи это и сделай с этим что-то".
👉 Supplier — дай что-то
Supplier ничего не принимает и возвращает значение.
Часто используется для получения конфигураций, генерации ID, ленивого создания объектов и прочего.
То есть:
"выдай нужную штуку, когда я попрошу".
👉 Function<T, R> — преобразуй что-то
Принимает значение типа T и возвращает значение типа R.
На практике эта штука — самая распространённая.
По смыслу:
"получаю T, возвращаю R".
👉 Predicate — реши что-то (true/false)
Принимает значение и возвращает boolean.
Часто нужен для фильтрации списков, простых проверок, валидаций, правил.
То есть:
"подходит или не подходит под условие".
Важно:
Эти интерфейсы существуют не ради компактного кода.
Они нужны, чтобы ты думал через операции, а не через классы.
Они идеально заходят в Streams, в коллбеки, в валидации, в композицию логики — везде, где есть простая операция, для которой не нужна отдельная сущность.
Это не замена всему на свете. Речь не про то, чтобы переписать всю систему в функциональном стиле.
Но они реально помогают во множестве сценариев.
Если научишься читать Function, Consumer, Supplier и Predicate, то спокойно разберёшь и напишешь современный Java-код без лишних страданий. И это уже хороший шаг вперёд.😁
👉 Java Portal
Сегодня разберём 4 штуки, которые встречаются чаще всего. Если поймёшь их, писать код станет современнее, местами чище
Идея простая
Функциональный интерфейс — это интерфейс с одним абстрактным методом. Благодаря этому его можно реализовать через лямбды.
В Java их много, но вот четыре, которые ты будешь видеть постоянно:
Consumer принимает значение и ничего не возвращает.
Отлично подходит для побочных эффектов: логирование, вывод в консоль, сохранение, отправка и так далее.
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("Hola");
Проще говоря:
"получи это и сделай с этим что-то".
Supplier ничего не принимает и возвращает значение.
Часто используется для получения конфигураций, генерации ID, ленивого создания объектов и прочего.
Supplier<Double> supplier = () -> Math.random();
supplier.get();
То есть:
"выдай нужную штуку, когда я попрошу".
Принимает значение типа T и возвращает значение типа R.
На практике эта штука — самая распространённая.
Function<Integer, String> function = number -> "N° " + number;
function.apply(5);
По смыслу:
"получаю T, возвращаю R".
Принимает значение и возвращает boolean.
Часто нужен для фильтрации списков, простых проверок, валидаций, правил.
Predicate<String> predicate = s -> s.length() > 5;
predicate.test("Java");
То есть:
"подходит или не подходит под условие".
Важно:
Эти интерфейсы существуют не ради компактного кода.
Они нужны, чтобы ты думал через операции, а не через классы.
Они идеально заходят в Streams, в коллбеки, в валидации, в композицию логики — везде, где есть простая операция, для которой не нужна отдельная сущность.
Это не замена всему на свете. Речь не про то, чтобы переписать всю систему в функциональном стиле.
Но они реально помогают во множестве сценариев.
Если научишься читать Function, Consumer, Supplier и Predicate, то спокойно разберёшь и напишешь современный Java-код без лишних страданий. И это уже хороший шаг вперёд.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤4🔥2🤣1
Кандидаты часто путаются, когда спрашивают:
На самом деле всё очень просто.
* Rate limiting
Задаёт фиксированный максимум запросов.
Если превысил лимит — лишние запросы просто блокируются.
Пример: 100 запросов в минуту разрешено. 101-й — отклоняется.
* Throttling
Не блокирует. Он замедляет обработку запросов, когда ты начинаешь спамить.
Пример: после 100 запросов каждый следующий обрабатывается с задержкой, но всё равно проходит.
Запомнить легко:
Rate limiting — стоп сверхлимита.
Throttling — замедление сверхлимита.
👉 Java Portal
в чем разница между rate limiting и throttling?
На самом деле всё очень просто.
* Rate limiting
Задаёт фиксированный максимум запросов.
Если превысил лимит — лишние запросы просто блокируются.
Пример: 100 запросов в минуту разрешено. 101-й — отклоняется.
* Throttling
Не блокирует. Он замедляет обработку запросов, когда ты начинаешь спамить.
Пример: после 100 запросов каждый следующий обрабатывается с задержкой, но всё равно проходит.
Запомнить легко:
Rate limiting — стоп сверхлимита.
Throttling — замедление сверхлимита.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🤔1
Совет по Java: в JPA можно использовать native query не только для SELECT, но и для UPDATE, DELETE и INSERT. Учти, что в этом случае всё проходит мимо EntityManager и PersistenceContext.
👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🔥3👍2
Одна из самых опасных проблем в распределенных системах это двойная запись:
сохранить что-то в своей базе и параллельно опубликовать событие в другой системе, рассчитывая что оба шага пройдут вместе.
А что если нет?
Представь классический поток:
1️⃣ Сохраняешь заказ в базе.
2️⃣ Отправляешь событие в Kafka или делаешь запрос в другую API, чтобы сообщить что заказ создан.
Где проблема?
👉 Если шаг 1 прошел, а шаг 2 упал:
База говорит заказ создан, но внешний сервис об этом не знает.
👉 Если шаг 2 прошел, а шаг 1 упал:
Ты опубликовал фантомное событие о том, чего нет.
Это и есть двойная запись.
И если думаешь что это тебя не коснется, просто подожди пока прод покажет тебе реальность.
Тут и появляется Transactional Outbox Pattern.
Что именно делает этот паттерн?🤔
Избавляет от необходимости писать в два места одновременно.
Превращает внешнюю запись в надежный процесс.
Идея простая, вот минимальный способ это реализовать:
1. Когда сохраняешь данные в базе (INSERT/UPDATE),
ты параллельно пишешь событие в отдельную таблицу, например outbox.
2. Оба INSERT выполняются в одной транзакции.
Если что-то падает, падает все.
Так достигается гарантированное консистентное состояние.
3. Затем отдельный процесс (poller или scheduler) читает эту таблицу и публикует реальное событие в Kafka, RabbitMQ или куда нужно.
4. Если публикация упала, ничего страшного.
Событие остается в таблице пока его не получится отправить.
С этим достаточно простым потоком ты получаешь консистентность без двойной записи.
Почему это так хорошо работает?
Потому что принимает неприятную правду:
Нельзя рассчитывать что два разных системы корректно обработают одну и ту же транзакцию.
❌ База умеет в транзакции.
❌ Kafka — нет.
❌ Rabbit — нет.
❌ Webhook тем более.
Поэтому решение не в том чтобы это «продавить», а в том чтобы адаптировать архитектуру к реальности:
Единственная запись, которой реально можно доверять — запись в твою базу.
Все внешнее (то что не хранится в твоей базе) должно выполняться позже, с ретраями, логами и прочим.
Но да, это не бесплатно.
Нужно:
—> Создать таблицу outbox.
—> Настроить ретраи.
—> Удалять обработанные события.
—> Мониторить poller (или любую другую реализацию).
—> Не допустить двойные вставки в outbox чтобы избежать дублей.
И ключевая мысль:
Цена Transactional Outbox намного ниже цены ручной починки рассинхрона между сервисами и системами.
А это в проде дороже золота.
👉 Java Portal
сохранить что-то в своей базе и параллельно опубликовать событие в другой системе, рассчитывая что оба шага пройдут вместе.
А что если нет?
Представь классический поток:
Где проблема?
База говорит заказ создан, но внешний сервис об этом не знает.
Ты опубликовал фантомное событие о том, чего нет.
Это и есть двойная запись.
И если думаешь что это тебя не коснется, просто подожди пока прод покажет тебе реальность.
Тут и появляется Transactional Outbox Pattern.
Что именно делает этот паттерн?
Избавляет от необходимости писать в два места одновременно.
Превращает внешнюю запись в надежный процесс.
Идея простая, вот минимальный способ это реализовать:
1. Когда сохраняешь данные в базе (INSERT/UPDATE),
ты параллельно пишешь событие в отдельную таблицу, например outbox.
2. Оба INSERT выполняются в одной транзакции.
Если что-то падает, падает все.
Так достигается гарантированное консистентное состояние.
3. Затем отдельный процесс (poller или scheduler) читает эту таблицу и публикует реальное событие в Kafka, RabbitMQ или куда нужно.
4. Если публикация упала, ничего страшного.
Событие остается в таблице пока его не получится отправить.
С этим достаточно простым потоком ты получаешь консистентность без двойной записи.
Почему это так хорошо работает?
Потому что принимает неприятную правду:
Нельзя рассчитывать что два разных системы корректно обработают одну и ту же транзакцию.
Поэтому решение не в том чтобы это «продавить», а в том чтобы адаптировать архитектуру к реальности:
Единственная запись, которой реально можно доверять — запись в твою базу.
Все внешнее (то что не хранится в твоей базе) должно выполняться позже, с ретраями, логами и прочим.
Но да, это не бесплатно.
Нужно:
—> Создать таблицу outbox.
—> Настроить ретраи.
—> Удалять обработанные события.
—> Мониторить poller (или любую другую реализацию).
—> Не допустить двойные вставки в outbox чтобы избежать дублей.
И ключевая мысль:
Цена Transactional Outbox намного ниже цены ручной починки рассинхрона между сервисами и системами.
А это в проде дороже золота.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤2
Совет по Java: старайся не шарить данные между потоками.
Используй неизменяемые объекты
Или строй взаимодействие потоков через сообщения, без прямой модификации общего состояния
С неизменяемыми объектами:
Обмен сообщениями:
👉 Java Portal
Используй неизменяемые объекты
Или строй взаимодействие потоков через сообщения, без прямой модификации общего состояния
С неизменяемыми объектами:
record Book(String title, int price) {} // Immutable
public class BookJob implements Runnable {
private final Book book;
... // constructor
@Override
public void run() {
System.out.println(book.title() + " " + book.price());
}
}Обмен сообщениями:
...
new Thread(() -> {
try {
queue.put("mess1");
} catch (InterruptedException e) {}
}).start();
...
new Thread(() -> {
try {
String mess = queue.take();
} catch (InterruptedException e) {}
}).start();
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤔2
image_2025-12-04_09-41-20.png
1.4 MB
Знали, что у EnableWebSecurity есть параметр debug, который включает отладку безопасности?
🤯
Это помогает разобраться, что вообще происходит во время разработки.
Но в проде такое включать нельзя.
👉 Java Portal
Это помогает разобраться, что вообще происходит во время разработки.
Но в проде такое включать нельзя.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Совет по Java: начиная с Java 19 можно потрогать фичу под названием virtual threads.
Virtual threads это лёгкие потоки, которые управляются JVM, а не операционной системой.
JVM умеет приостанавливать и возобновлять выполнение без лишней траты ресурсов ОС.
IO-вызовы не блокируют потоки ОС.
Виртуальные потоки работают поверх небольшого пула платформенных потоков.
Пример:
👉 Java Portal
Virtual threads это лёгкие потоки, которые управляются JVM, а не операционной системой.
JVM умеет приостанавливать и возобновлять выполнение без лишней траты ресурсов ОС.
IO-вызовы не блокируют потоки ОС.
Виртуальные потоки работают поверх небольшого пула платформенных потоков.
Пример:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
Thread.sleep(1000);
});
}
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4
Нужна API, чтобы попрактиковаться в программировании?
Это, пожалуй, лучший вариант для приложений с прогнозом погоды.
✓ Полностью бесплатная
✓ Без регистрации и без API-ключа
✓ Работает по HTTPS и с включённым CORS
Можно тренироваться с JavaScript, Python, Java и чем угодно ещё:
→ open-meteo.com
👉 Java Portal
Это, пожалуй, лучший вариант для приложений с прогнозом погоды.
✓ Полностью бесплатная
✓ Без регистрации и без API-ключа
✓ Работает по HTTPS и с включённым CORS
Можно тренироваться с JavaScript, Python, Java и чем угодно ещё:
→ open-meteo.com
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥2🤯1
Java-совет: ты можешь использовать LinkedHashMap, чтобы легко реализовать LRU-кеш (Least Recently Used).
Тебе просто нужно заинстанцировать это вот так:
👉 Java Portal
Тебе просто нужно заинстанцировать это вот так:
int capacity = 3;
Map<Integer, String> cache = new LinkedHashMap<>(capacity, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
};
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(1);
cache.put(4, "four"); // 2 удаляется (наименее недавно использованный)
System.out.println(cache);
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤5🤔1
freeCodeCamp выкатили бесплатный курс по Git и GitHub для новичков. За 1 час разберёшь базу: ветки, слияния, pull request’ы и базовую командную работу. Отличный быстрый вход для тех, кто откладывал Git «на потом».
Git-курс тут
👉 Java Portal
Git-курс тут
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Одна идея, на которой я люблю делать акцент, это автобоксинг и анбоксинг в Java.
Java фактически дает нам два параллельных мира:
Примитивы: int, long, double = быстрые, компактные, живут на стеке.
Обертки: Integer, Long, Double = полноценные объекты в heap.
Между этими мирами Java делает одно автоматическое преобразование:
Автобоксинг - превращение примитива в объект
Анбоксинг - обратное преобразование
Работает удобно, пока в какой-то момент не начинает мешать.
Почему это может быть проблемой?
Потому что при каждой конверсии у Java появляется лишняя работа:
Создаются новые объекты
Увеличивается нагрузка на GC
CPU делает больше операций
Срываются JIT-оптимизации
В циклах с высокой нагрузкой появляются скрытые задержки
В большинстве приложений это незаметно, но если проявится, то обычно довольно резко.
Типичный пример - коллекции:
Очевидно, что пример синтетический, но суть в том, что в этом цикле i упаковывается в Integer миллион раз.
Перенеси этот сценарий на реальные участки твоего кода.
Каждая конверсия создает объект, нагружает GC и сбивает процессорный кеш.
Из-за этого приложение начинает подвисать, когда GC активируется.
Коллекции - не единственный источник проблем. Есть и сравнения:
При работе с обертками оператор == сравнивает ссылки, а не значения.
Если происходит неявный анбоксинг, оба значения сначала превращаются в int и только потом сравниваются.
Такое смешение поведения местами непредсказуемо и приводит к трудноуловимым багам.
Есть еще и лямбды:
Если работаете с числами, лучше брать примитивные стримы:
IntStream
LongStream
DoubleStream
Они экономят память, убирают боксинг и работают быстрее.
Худший сценарий - структуры подсчета:
Каждая операция делает:
Анбоксинг существующего значения
Сложение как примитива
Обратный автобоксинг результата
Масштабируется это плохо и убивает производительность при высокой конкуренции.
Хорошие варианты:
- AtomicInteger
- LongAdder
- Хранить счетчик как примитив и конвертировать только при выдаче наружу
Значит ли это, что обертки плохие? Нет.
Проблема не в них, а в случайном использовании.
Обертки используют, когда:
Нужны null-значения
API требует объектные типы
Есть доменная логика, где важна идентичность или отсутствие значения
Примитивы используют, когда:
Есть большие циклы
Много повторных вычислений
Важно избегать лишнего GC
Нужна стабильная производительность
Автобоксинг удобен, но у него есть подводные камни.
Обычно он не мешает, но когда все-таки мешает, его эффекты сложно отследить.
Хорошая новость: исправляется это просто.
Используй примитивы в вычислениях
Используй примитивные стримы
Не применяй обертки в счетчиках
Проверь циклы, создающие лишние объекты
Пара мелких правок может убрать тысячи лишних объектов и заметно снизить нагрузку на приложение.
👉 Java Portal
Java фактически дает нам два параллельных мира:
Примитивы: int, long, double = быстрые, компактные, живут на стеке.
Обертки: Integer, Long, Double = полноценные объекты в heap.
Между этими мирами Java делает одно автоматическое преобразование:
Автобоксинг - превращение примитива в объект
Анбоксинг - обратное преобразование
Работает удобно, пока в какой-то момент не начинает мешать.
Почему это может быть проблемой?
Потому что при каждой конверсии у Java появляется лишняя работа:
Создаются новые объекты
Увеличивается нагрузка на GC
CPU делает больше операций
Срываются JIT-оптимизации
В циклах с высокой нагрузкой появляются скрытые задержки
В большинстве приложений это незаметно, но если проявится, то обычно довольно резко.
Типичный пример - коллекции:
List<Integer> numeros = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
numeros.add(i);
}
Очевидно, что пример синтетический, но суть в том, что в этом цикле i упаковывается в Integer миллион раз.
Перенеси этот сценарий на реальные участки твоего кода.
Каждая конверсия создает объект, нагружает GC и сбивает процессорный кеш.
Из-за этого приложение начинает подвисать, когда GC активируется.
Коллекции - не единственный источник проблем. Есть и сравнения:
Integer a = 1000;
Integer b = 1000;
if (a == b) { ... }
При работе с обертками оператор == сравнивает ссылки, а не значения.
Если происходит неявный анбоксинг, оба значения сначала превращаются в int и только потом сравниваются.
Такое смешение поведения местами непредсказуемо и приводит к трудноуловимым багам.
Есть еще и лямбды:
Stream<Integer> s = IntStream.range(0, 1_000_000)
.boxed(); // миллион автобоксингов
Если работаете с числами, лучше брать примитивные стримы:
IntStream
LongStream
DoubleStream
Они экономят память, убирают боксинг и работают быстрее.
Худший сценарий - структуры подсчета:
Map<String, Integer> counter = new HashMap<>();
counter.put(key, counter.getOrDefault(key, 0) + 1);
Каждая операция делает:
Анбоксинг существующего значения
Сложение как примитива
Обратный автобоксинг результата
Масштабируется это плохо и убивает производительность при высокой конкуренции.
Хорошие варианты:
- AtomicInteger
- LongAdder
- Хранить счетчик как примитив и конвертировать только при выдаче наружу
Значит ли это, что обертки плохие? Нет.
Проблема не в них, а в случайном использовании.
Обертки используют, когда:
Нужны null-значения
API требует объектные типы
Есть доменная логика, где важна идентичность или отсутствие значения
Примитивы используют, когда:
Есть большие циклы
Много повторных вычислений
Важно избегать лишнего GC
Нужна стабильная производительность
Автобоксинг удобен, но у него есть подводные камни.
Обычно он не мешает, но когда все-таки мешает, его эффекты сложно отследить.
Хорошая новость: исправляется это просто.
Используй примитивы в вычислениях
Используй примитивные стримы
Не применяй обертки в счетчиках
Проверь циклы, создающие лишние объекты
Пара мелких правок может убрать тысячи лишних объектов и заметно снизить нагрузку на приложение.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Похоже, это претендент на TUI года ⭐️
gitlogue — инструмент для «кинематографичного» воспроизведения Git-коммитов прямо в терминале.
Смотри, как коммиты развертываются с анимацией ввода, подсветкой синтаксиса и живым обновлением дерева файлов.
Буквально наблюдай, как твой репозиторий пишет себя сам.
Написан на Rust
GitHub: https://github.com/unhappychoice/gitlogue
👉 Java Portal
gitlogue — инструмент для «кинематографичного» воспроизведения Git-коммитов прямо в терминале.
Смотри, как коммиты развертываются с анимацией ввода, подсветкой синтаксиса и живым обновлением дерева файлов.
Буквально наблюдай, как твой репозиторий пишет себя сам.
Написан на Rust
GitHub: https://github.com/unhappychoice/gitlogue
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8