Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
Show more📈 Analytical overview of Telegram channel Java: fill the gaps
Channel Java: fill the gaps (@java_fillthegaps) in the Russian language segment is an active participant. Currently, the community unites 12 552 subscribers, ranking 10 101 in the Technologies & Applications category and 52 755 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 12 552 subscribers.
According to the latest data from 05 June, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -49 over the last 30 days and by -4 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 34.71%. Within the first 24 hours after publication, content typically collects N/A% reactions from the total number of subscribers.
- Post reach: On average, each post receives 0 views. Within the first day, a publication typically gains 0 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 0.
- Thematic interests: Content is focused on key topics such as redis, hashmap, linkedhashmap, индекс, фича.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
Thanks to the high frequency of updates (latest data received on 07 June, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
Работает пока только для Linux, иногда показывает небольшой прирост скорости. Так что имеет смысл попробовать!
В GraalVM есть ещё одно интересное направление, и о нем расскажу в следующем посте🔥Курс великолепный, не пожалел ни одного рубля, что потратил. Это уникальный курс в своем сегменте, особенно на русском рынке. Всем советую, на курсе вы найдете все ответы на интересующие вас вопросы. Более того из курса вы сможете узнать то, что просто нет в открытом доступе нигде, исключительно авторские наработки. Однозначно советую всем бэкэнд разработчикам, даже если вы не особо используете многопточку - это очень поможет вам в понимании многопоточного кода фреймворков и вообще сильно улучшит ваш кругозор.Так что с многопоточкой всё давно решено, все ссылки в закрепе. Остальные ответы ещё разгребаю, буду писать посты по мере сил. Ещё раз спасибо, очень ценю вашу помощь❤️
SELECT * FROM t ORDER BY …
LIMIT 100
Для второй и последующих добавляем offset:
SELECT * FROM t ORDER BY …
LIMIT 100 OFFSET 100
Offset показывает, сколько строк пропустить.
В чем подвох: кажется, что при offset 100 постгрес сразу прыгнет на 101 строку, но это не так. Он честно пройдет 100 строк, и потом отдаст следующие 100.
❌ В случае большого оффсета такая операция будет работать долго даже при наличии индекса
✅ Просто реализовать. Минимум действий со стороны клиента и поддержка в фреймворках.
🌸 Keyset пагинация. Она же cursor, она же seek.
Здесь мы используем where в паре с индексом и сразу прыгаем на нужную позицию. Первую страницу получаем так:
SELECT * FROM t ORDER BY name
LIMIT 10
Если записи отсортированы по имени, запоминаем имя последнего элемента. Пусть это будет х. Чтобы получить вторую страницу, добавляем where:
SELECT * FROM t ORDER BY name
WHERE name > x
LIMIT 10
✅ Работает быстрее, чем вариант с оффсетом. Чем больше оффсет, тем значительнее разница
❌ Сложнее в реализации, надо запоминать поле в последней пачке
❌ Нельзя перейти к произвольной странице, только последовательная выдача данных
Когда что использовать?
Во многих докладах/статьях часто встречается мысль, что оффсет это ужас, и надо всегда использовать кейсет. На практике все не так категорично:
▫️ Если на сайте infinite scrolling или пользователь вряд ли будет много смотреть, вполне ок использовать простую оффсет пагинацию.
▫️ Если данные выкачиваются большими пачками для обработки, аналитики, миграции и тд, берём keyset пагинацию.
▫️ Если надо выгрузить много данных и прыгать по ним туда-сюда или у вас другой сложный случай, придется экспериментировать с индексами и запросами. Этот доклад хорошо показывает, что пагинация - далеко не тривиальная задача.
А поставить огонек качественному контенту очень просто, поэтому это нужно обязательно сделать🔥public int compare(T o1, T o2) {…}
Если метод вернул
▫️ число больше нуля — первый элемент больше второго
▫️ 0 — элементы равны
▫️ число меньше нуля — первый меньше второго
Простейшая и популярная реализация — вычесть одно значение из другого:
(o1, o2) -> (int) (o1.getSum() - o2.getSum())
❓ Что с этим не так?
Я всегда сомневаюсь, что из чего вычитать. Если вы отвечали на опрос дольше одной секунды, значит мы в одном лагере:) Компаратор — совсем не то место, где мозг должен спотыкаться.
В Java 8 в интерфейсе Comparator появился удобный метод:
orders.sort(comparing(Order::getSum))
Что классно:
✅ Не надо вспоминать, что из чего вычитать
✅ Легко сделать сравнение в обратном порядке:
comparing(Order::getSum).reversed()
✅ Можно учесть null:
nullsFirst(comparing(Order::getSum))
nullLast(…)
✅ Удобно сортировать по нескольким полям:
comparing(Order::getSum).thenComparing(Order::getId)
Самостоятельно обрабатывать null и писать сложные сортировки очень утомительно. Помню, как с удовольствием удаляла из проекта компараторы на 20 строк после перехода на Java 8😊
Важные нюансы:
1️⃣ comparing*
В интерфейсе Comparator также доступны методы comparingInt, comparingLong и comparingDouble. Используются для полей примитивного типа, чтобы избежать лишнего боксинга. Если в классе Order
Long id → используем comparing(Order::getId)
long id → comparingLong(Order::getId)
Не указывайте тип лишний раз. Для работы с объектами подойдёт обычный comparing
2️⃣ Нетривиальная работа с null*
В обычных методах легко понять, что происходит:
comparing(A).reversed().thenComparing(Б)
=
отсортировать по полю А в обратном порядке, дубликаты отсортировать по Б
Методы null* выбиваются из этой схемы.
nullsFirst(comparing(Order::getSum))
означает, что первыми будут null объекты, а существующие заказы отсортируются по сумме. Этот компаратор работает для такого кода:
orders.add(null); // эти элементы будут впереди
orders.add(new Order(…)); // эти отсортируются по полю sum
Если в списке нет null объектов, но в поле sum возможен null, придётся писать так:
…comparing(Order::getSum, nullsFirst(naturalOrder()));
Сравнение по нескольким nullable полям выглядит совсем плохо. К счастью, на практике такие задачи встречаются редко.
Ответ на вопрос перед постом:
(o1, o2) -> (int) (o2.getSum() - o1.getSum())
Но лучше использовать comparing(Order::getSum).reversed() ✨void sort(Comparator<?super Е> c) {…}
Для элементарной операции сортировки чисел приходится писать
list.sort(Comparator.naturalOrder())
Код с Comparator.naturalOrder() похож на какой-то костыль. Под капотом не происходит ничего особенного, реализация компаратора очень простая:
(с1, с2) -> c1.compareTo(c2)
❓ Так зачем писать так сложно? Почему в интерфейсе List нет метода sort()?
Сейчас расскажу:)
Java создавался как язык для больших и долгоживущих приложений, и его основные ценности — стабильность и обратная совместимость.
C начала 2000-х в JDK есть метод Collections.sort(List). Статический метод, который меняет внутреннее состояние аргумента. Сейчас это порицается, но в те времена было норм.
В больших компаниях классы JDK часто расширяли удобными методами, в том числе сортировкой в функциональном стиле:
CustomList sorted = list.sort();
Спустя много лет стало понятно, что экземплярные методы сортировки — это классно, и надо добавить такой метод в JDK. Чтобы текущие реализации списков не сломались, это должен быть дефолтный метод в интерфейсе List.
Но есть проблема. Допустим, на проекте есть такой класс:
public class CustomList implements List {
public CustomList sort() {…}
}
Допустим, в java 8 в интерфейс List добавили бы метод
default void sort() {…}
Старый метод не может переопределить дефолтный. тк возвращаемые значения не совместимы. Поэтому проекты, которые определили свой функциональный sort в начале 2000-х, перестанут компилироваться. Пользователи будут недовольны😡
Многие проекты полагаются на свой sort, поэтому разработчики JDK не стали добавлять его в интерфейс. Метод sort(Comparator) использовался редко, поэтому теперь он с нами.
У Stream API нет проблем с совместимостью, так что для стримов есть прекрасный метод sorted(). Для коллекций метод sorted() есть в Kotlin💖
(обратите внимание на суффикс -ed, всё по правилам функционального подхода)
Ответ на вопрос перед постом: отсортировать список можно так:
✅ list.sort(Comparator.naturalOrder());
✅ list = list.stream().sorted().toList();
Если вам понравился list.sort(), значит у вас хороший вкус на API. К сожалению, у java свои загоны, поэтому этого метода в JDK нет.WITH cte_name AS (
SELECT …
)
SELECT … FROM cte_name;
cte_name используется как источник данных
⭐️ VIEW и MATERIALIZED VIEW выглядят похоже:
CREATE (MATERIALIZED) VIEW view_name AS
SELECT … ;
Полученное вью также используется как источник данных:
SELECT … FROM view_name;
В чём же разница?
1️⃣ Что именно хранится
VIEW — это просто сокращение запроса, результат выполнения не сохраняется. При каждом FROM view_name запрос выполняется заново.
MATERIALIZED VIEW сохраняет результат на момент выполнения. По сути создаётся временная таблица с копией данных.
Важный момент: если исходные данные поменяются, они не повлияют на данные в MATERIALIZED VIEW. Для актуализации данных надо отдельно вызвать команду REFRESH.
СТЕ как MATERIALIZED VIEW сохраняет результат выполнения и считается один раз.
2️⃣ Видимость
CTE не существует сам по себе, за ним обязательно должен следовать запрос, который его использует. Можно сказать, что область видимости и время жизни CTE — один запрос.
VIEW и MATERIALIZED VIEW доступны на уровне схемы, можно пользоваться много раз из разных мест. Удалять вью надо явно командой DROP.
Это основные отличия. Есть ещё несколько, но не будем углубляться:)
Примерные кейсы использования:
🔧 CTE — сделать сложные запросы более читаемыми
🔨 View — синоним для популярного запроса на выборку
🪛 Materialized view — снимок данных, которые долго считать с нуля, но к которым много обращений
Теперь закрепим знания из этого и предыдущего поста небольшой задачкой✍️
Есть 2 таблицы:
🔸 from_table со столбцами id, firstname, lastname.
🔸 to_table со столбцами id, name
Задача: перенести все строки из from_table в to_table, соединив firstname и lastname в одно поле name. После выполнения запроса from_table должен стать пустым.
Попробуйте выполнить задачу ОДНИМ запросом.
Онлайн Postgres: pgplayground
Исходный код для экспериментов:
CREATE TABLE from_table(id int, firstname text, lastname text);
INSERT INTO from_table VALUES(1, 'F1', 'L1');
CREATE TABLE to_table(id int, name text);
✨ РЕШЕНИЕ ✨
Вспоминаем, что DELETE возвращает удалённые строки. Формируем из них СТЕ и передаём в INSERT:
WITH deleted_rows AS (
DELETE FROM from_table
RETURNING id, firstname || ' ' || lastname
)
INSERT INTO to_table
SELECT * FROM deleted_rows;SELECT * FROM table1
INNER JOIN table2 USING (user_id);
2️⃣ INSERT + SELECT
Чтобы вставить в одну таблицу значения из другой, нет смысла вытаскивать данные отдельно. INSERT и SELECT прекрасно комбинируются:
INSERT INTO users (id, name)
SELECT user_id, fullname FROM customers;
3️⃣ RETURNING
Чтобы после вставки вернуть новые строки, отдельный SELECT не нужен. Добавьте RETURNING для нужных столбцов:
INSERT INTO users VALUE (…)
RETURNING id;
Обычно возвращают id, но можно вернуть несколько столбцов и даже *.
Для DELETE RETURNING тоже работает и возвращает удалённые строки.
4️⃣ Тестовые данные
Для генерации простейших тестовых данных через SQL вам пригодятся:
🔸 generate_series(a, b) — последовательность целых чисел от a до b. Той же функцией генерятся даты в заданном диапазоне, синтаксис смотрите в документации
🔸 random() — случайное число от 0 до 1
🔸 md5(а) — хэш числа а. Помогает превратить результат random() в строку
Дальше комбинируем. Например, так:
INSERT INTO t
SELECT id, // 1, 2,…, 100
'name' || id, // name1,…, name100
random(),
md5(random()::text)
FROM generate_series(1,100) id;
Одним запросом получаем 100 строк в базе!
Приёмы выше хоть и простые, но пригодятся на большинстве проектов. Пользуйтесь и ставьте огонёчек, если нужна вторая часть🔥Ctrl + F12
Быстро найти нужный метод или узнать, что вообще умеет класс
🚀 Найти класс или файл в проекте: Shift-Shift
Откроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению: Ctrl + B
Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
Ctrl + Alt + ⬅️ Ctrl + Alt + ➡️IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов 🚀 Найти строку по номеру:
Ctrl + G
Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строку
Очень удобные шорткаты, обязательно попробуйте🔥
Available now! Telegram Research 2025 — the year's key insights 
