Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
显示更多📈 Telegram 频道 Java: fill the gaps 的分析概览
频道 Java: fill the gaps (@java_fillthegaps) 俄语 语言赛道中的 是活跃参与者。目前社区聚集了 12 552 名订阅者,在 技术与应用 类别中位列第 10 101,并在 俄罗斯 地区排名第 52 755 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 12 552 名订阅者。
根据 05 六月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -49,过去 24 小时变化为 -4,整体触达仍然可观。
- 认证状态: 未认证
- 互动率 (ER): 平均受众互动率为 34.71%。内容发布后 24 小时内通常能获得 N/A% 的反应,占订阅者总量。
- 帖子覆盖: 每篇帖子平均可获得 0 次浏览,首日通常累积 0 次浏览。
- 互动与反馈: 受众积极参与,单帖平均反应数为 0。
- 主题关注点: 内容集中在 redis, hashmap, linkedhashmap, индекс, фича 等核心主题上。
📝 描述与内容策略
作者将该频道定位为表达主观观点的平台:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
凭借高频更新(最新数据采集于 07 六月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
-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", можно не проматывать огромный класс, а быстро перейти на нужную строку
Очень удобные шорткаты, обязательно попробуйте🔥
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
