ch
Feedback
Java: fill the gaps

Java: fill the gaps

前往频道在 Telegram

Привет! Меня зовут Диана, и я занимаюсь разработкой с 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),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。

12 552
订阅者
-424 小时
-257
-4930
帖子存档
Можно ли переопределить метод getClass() в классе Object?
Anonymous voting

Популярная ошибка в блокировках, часть 2 Продолжим разбирать ошибки при распределении задач. В прошлом посте мы разобрали, что не нужно отпускать блокировку слишком рано, иначе одну задачку возьмут несколько сервисов. Сегодня разберём следующую проблему: Задачи в разных сервисах не обрабатываются параллельно Почему? 🧑‍💻 Сервис 1 выполняет запрос "найди задачу для обработки", получает строку 1 и блокирует её 🧑‍💻 Сервис 2 выполняет такой же запрос, получает в результате ту же строку. Но блокировку поставить не может, так как строка уже заблокирована. 💅 Сервис 2 ждёт, пока блокировка снимется В итоге все сервисы ждут, пока сервис 1 закончит задачу 1 и отпустит блокировку. С другими задачами ситуация повторится - один сервис работает, остальные висят на блокировке. Не самая эффективная командная работа🙈 Чтобы исправить ситуацию, сервисы должны пропускать заблокированные строки и брать себе задачу из "свободных". При взятии блокировки нужно добавить SKIP LOCKED. Но средствами Spring Data SKIP LOCKED не сделать, поэтому напишем SQL запрос над методом в репозитории:
@Query(value = "SELECT * FROM outbox 
          WHERE is_done = false 
          ORDER BY id ASC LIMIT 1 
          FOR UPDATE SKIP LOCKED",
    nativeQuery = true
)

Optional<OutboxEntry> findFirstByIsDoneFalseOrderByIdAsc();
Теперь задачи обрабатываются параллельно несколькими сервисами🥳 P.S. Очень рада, что в прошлом опросе было много правильных ответов, вы умнички🥰

Какая проблема возможна в коде выше?
Anonymous voting

Продолжение прошлого вопроса. В БД складываются задачи, которые затем распределяются между несколькими сервисами. Сервис извлекает задачи и обрабатывает их примерно так👇 Над Scheduled задачей добавилась аннотация Transactional. Какая проблема возможна в этом коде?

Популярная ошибка в блокировках, часть 1 Недавно делала ревью одного сервиса и нашла там ошибки, связанные с блокировками. Я встречала подобные ошибки и на других проектах, поэтому давайте разберём их вместе, будет полезно:) Исходная цель: распределить задачи между сервисами. Решается очень просто: ✍️ Складываем задачи в отдельную таблицу 🙋 Каждый сервис выбирает из таблицы одну строку и ставит на неё блокировку. Благодаря блокировке другой сервис не может взять эту задачу в работу. Примерно такая реализация и сделана на картинке выше. С виду вроде всё хорошо, но есть две проблемы. Первая - задача может быть обработана несколько раз💔 🤔 Почему? Блокировка действует до конца транзакции. В коде выше транзакция заканчивается после получения нужной строки, затем блокировка снимается. И по сути блокировки нет. Одну задачу могут взять несколько сервисов и обработать её несколько раз. Что делать: ✅ Поставить аннотацию Transactional над Scheduled методом. Тогда блокировка держится во время всей работы над задачей Плюс: просто реализовать Минус: если обработка сложная, мы долго и нерационально держим соединение с базой ✅ Альтернатива — отказаться от блокировок. Берём задачу в работу — меняем статус в БД на "in progress". Делается одним запросом с помощью RETURNING:
UPDATE tasks SET status = 'in progress'
WHERE status = 'not processed'
RETURNING id, …;
Плюс: нет длинной транзакции Минус: если сервис упадёт, задача останется в БД со статусом "в работе", и в итоге не будет обработана. Нужно дополнительно следить за такими ситуациями. Итого Блокировки работают до конца работы транзакции. Если полагаетесь на блокировки - не отпускайте их раньше времени. Это просто, но в большой кодовой базе легко упустить этот момент. Будьте внимательнее❤️ Вторую проблему опишу в следующем посте!

Какая проблема возможна в коде выше?
Anonymous voting

В БД складываются задачи, которые затем распределяются между несколькими сервисами. Сервис извлекает задачи и обрабатывает их примерно так👇 Какая проблема возможна в этом коде?

Dependency Inversion Сегодня расскажу, в чем суть принципа DI из SOLID, и почему с ним так много проблем. Вспомним формулировку: ✍️ Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. ✍️ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Ставь огонёк, если даже не вчитывался в этот набор слов😊 Формулировка и правда полный отстой. Первая часть про какие-то модули, зависящие от каких-то абстракций. Вторая напоминает инкапсуляцию. Роберт Мартин, он же дядюшка Боб, в целом молодец, но конкретно здесь плохо донёс свою идею. В оригинальной статье всё очень сумбурно. Интерфейсы добавляются на каждом шагу и отвлекают от сути dependency inversion. Ничего удивительного, что большинство людей не поняли, о чём речь. Дядюшка Боб не смог, тетя Диана объяснит🤌 Все очень просто. У каждого класса есть своя область ответственности, single responsibility: 🔴 Кнопку нажимают, и она отправляет сигнал об этом. Ничего больше 💡 Лампа включается и выключается, когда ей говорят. Ничего больше 💏 Взаимодействие этих сущностей должно быть описано отдельно Где здесь инверсия: В нашем мире все описывается последовательно, причина -> следствие. Нажали кнопку -> включилась лампа. Перекладывая на код, в классе Кнопка будет поле Лампа:
public class Button
   private Lamp lamp;
   public void push() {
       // включить/выключить лампу
   }
}
В мире с Dependency inversion Кнопка и Лампа ничего не знают друг о друге, логика взаимодействия описана в классе Электросхема. Она ловит сигналы от Кнопки и отправляет команды Лампе:
public class Circuit {
    private Button button;
    private Lamp lamp;
    public void process() {
        // получить сигнал от кнопки
        // сказать лампе включиться / выключиться
    }
}
Если у лампы появится новый режим, разбираться с этим будет электросхема, а не кнопка. Если кнопка поменяется на датчик движения, обработка поменяется только в электросхеме. Интерфейсы можно добавить, чтобы жонглировать реализациями, но это не главное. Dependency inversion говорит о том, что логика взаимодействия компонентов должна находиться НЕ в самих компонентах. Кстати, если перейти с уровня классов на уровень контроллеры/сервисы, получится Clean architecture. В основе та же идея: компоненты отдельно, взаимодействие отдельно. Всё очень просто❤️

Принципы SOLID и групповой транс Недавно слушала записи собеседований и обратила внимание на интересный феномен. Связан он, как вы уже догадались, с обсуждением SOLID. Типичный диалог на собеседовании выглядит так: 👨‍💼: Расскажите про SOLID 👱‍♂️: [расшифровка каждой буквы] 👨‍💼: Используете в работе? 👱‍♂️: Конечно! Хорошо, если на этом все заканчивается. Но бывает и продолжение: "расскажите принципы своими словами". На этом месте даже опытные и умные люди впадают в транс. Например, кандидат описывает принцип Open-closed: 👱‍♂️: Чтобы добавить новый метод, нужно не менять существующий класс, а создать новый 👨‍💼 : Угу, давайте дальше Ни кандидат, ни собеседующий не создают новый класс для каждого метода. Почему один так отвечает, а другой принимает такой ответ — загадка. Dependency Inversion часто объясняют как "нужно всё делать через интерфейс".
Map<String, String> map = new HashMap<>();
Это считается реализацией DI? Чем отличается от инкапсуляции? Для каждого класса нужно создавать интерфейс? Работа с любым классом напрямую — это нарушение принципа? Почему в названии есть инвершн, что инвертируется-то? Вопросы здравые, но люди в трансе:) У других абстрактных тем вроде принципов ООП или паттернов такого эффекта нет. SOLID — самая унылая тема на собеседовании. Формальная, абстрактная и скучная для всех сторон. Собеседование длится всего час-полтора, нет никакого смысла тратить время на ритуальный обмен фразами. ❓ Применяются ли принципы SOLID на практике? ▫️ Single Responsibility - да. Что неудивительно, это самый понятный и простой принцип. ▫️ Open-closed, Liskov и Interface Segregation тесно связаны с наследованием и сложными иерархиями классов. Для бизнес логики большинства систем это не очень актуально. Там максимум интерфейс и несколько реализаций. ▫️ Dependency Inversion - самый непонятный и недооцененный принцип. За 10+ лет в разработке я встречала мало людей, которые его поняли. Это не просто "делаем всё через интерфейс". Можно реализовать его даже без интерфейсов, суть вообще в другом. К четвергу напишу пост и подробно объясню🔥

Самая унылая тема на собеседовании это
Anonymous voting

Требуется Java эксперт⚡ Последнее время плотно занимаюсь новым проектом. Цель проста - помочь джавистам профессионально расти и быть на коне в текущих сложных условиях. Планы амбициозные, но на первом этапе буду работать над ультра востребованной темой - подготовке к собеседованиям. Хочу сделать этот процесс проще, лучше и приятнее. Заодно протестирую несколько концепций. Пойму, в ту ли сторону я смотрю и находит ли это отклик. Для реализации мне нужны помощники, поэтому открываю вакансию: Java эксперт. Ищу людей, которые ⭐ Знают и любят джава разработку ⭐ Работали минимум на 3 разных проектах и видели всякое ⭐ Критически мыслят, видят логические несостыковки ⭐ Умеют работать с первоисточниками и проверять информацию. Не боятся простыни текста на английском ⭐ Большой плюс, если вы часто проводите собеседования и видите, где у людей случаются затыки Что нужно делать: писать статьи по моему ТЗ. Сначала проработаем популярные темы типа HashMap, ACID, N+1, потом углубимся в другие нужные вещи. Это будет серьезный умственный труд. Естественно, оплачиваемый. Как присоединиться: ▫️ Заполнить анкету с небольшим тех.скринингом ▫️ Если все ок, я пришлю вам на почту оплачиваемое тестовое задание Если вам интересно в этом поучаствовать, заполняйте анкету, не стесняйтесь. Если у вас есть знакомый, который подходит под описание, пришлите ему этот пост❤️

Почему однопоточный Redis такой быстрый? В прошлом посте предложила вам задачку: сравнить Redis и велосипедик на основе ConcurrentHashMap + Spring MVC. ConcurrentHashMap — многопоточный, и вроде должен быть лучше. Но именно однопоточный Redis является базовым выбором для кэша. Как однопоточный Redis справляется с нагрузкой? Секрет в том, как он работает с запросами. Есть 2 основные модели: 🌊 Каждый запрос обрабатывается в своем потоке (thread per request). Такая модель используется, когда мы подключаем Spring MVC. Наш велосипедик тоже на ней работает. У каждого потока свой стэк, переменные изолированы. Код легко писать, читать и дебажить. Идеальный вариант для сложных энтерпрайзных задач! Но есть недостаток - число запросов в работе ограничено числом потоков в ОС. Обычно это несколько тысяч. Из-за этой модели наш велосипед и проигрывает: 😒 Миллионы запросов просто не дойдут до ConcurrentHashMap, максимум несколько тысяч. 😒 Прочитать и записать в мэп - простые операции. Отправлять таких малышей в отдельный поток - как забивать краном гвозди. Очень большие накладные расходы на каждый запрос. Redis использует другую модель: 🏃 EventLoop - малое число потоков бешено переключаются между запросами. В работу можно взять миллионы запросов! Такая схема используется в реактивных серверах типа Netty, поддерживает многопоточность в JS и питоне. Поэтому Redis и побеждает наш велосипед: возни с потоками нет, ограничений на запросы нет. Вся мощь процессора уходит на полезную работу, поэтому даже один поток справляется с большим объемом задач. ❓ Можно ли взять лучшее из двух миров? Использовать многопоточность вместе с EventLoop? Можно! Один поток Redis не использует все доступные ядра процессора, поэтому добавить десяток потоков - вполне рабочая идея. Такую схему используют KeyDB и DragonflyDB. На сайте публикуют бенчмарки, где они обходят Redis в 5-25 раз. 25 раз звучит слишком мощно, но про 5-10 раз можно верить. ❓ Почему чаще используется Redis, а не более быстрые альтернативы? Потому что Redis появился в 2009, используется на сотнях проектов и закрепился в сознании как базовое решение для кэша. Подводные камни известны, инфраструктура налажена, куча статей и докладов. KeyDB и DragonflyDB - свежие БД пирожки. Один вышел в 19 году, другой в 22. На конференциях особо не светились, громких кейсов внедрения пока нет. Энтерпрайз мир тяжело принимает новые технологии. Плюс не всегда нужно лучшее решение, иногда достаточно хорошего😊

Необычный вопрос на собеседовании Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам. Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил: 🧑‍🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый! У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто. Рассмотрим два сервиса: 🍓Первый - тот самый однопоточный Redis. 🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
public class MapController {
   private final Map<String, Object> map = new ConcurrentHashMap<>();

   @PostMapping
   public void putValue(@RequestParam String k, @RequestParam Object v) { 
      map.put(k, v); 
   }

   @GetMapping
   public Object getValue(@RequestParam String k) { 
      return map.get(k); 
   } 
}
Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти. В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊 Ещё немного вводных, чтобы глубже погрузиться в вопрос. Структура данных В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет. Многопоточный доступ В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток. ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет. Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом. Явный перевес в сторону нашего велосипеда🧐 На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно. Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️

Сколько операций на запись может одновременно проводить ConcurrentHashMap?
Anonymous voting

Почему в Set.of нельзя добавить дубликаты и null? Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку. Set.of вышел в рамках Java 9 в 2017 году. В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче. Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс. Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация. Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами. “Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of. Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types. Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично. Чем меня веселит эта история. Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет. Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода. Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄

Загадка Set.of🔮 В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления. Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2) Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем. Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой? Но это ещё не всё. Если убрать дубликаты и оставить код таким: Set.of(1, 2, null) получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯 Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
// 8
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
// 9
Set<String>set = Set.of(“a“, “b“);
Удобно, конечно, но альтернатива получилась не равнозначная. Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому? Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета: 🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика. Здесь можно потренировать критическое мышление и подумать, что не так с этой версией. А потом прочитать 3 причины: ✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
Set<Long> adminIds = Set.of(user1.getId(), user2.getId());
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально ✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации? Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом. Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊

Что будет в консоли при выполнении кода выше?
Anonymous voting

Что будет в консоли при выполнении кода ниже?

Коллекторы и тесты Сразу разберём вопрос выше. Можно ли безопасно заменить collect(toList()) на toList()? Ответ: нет, в коде выше получим ошибку в рантайме: ❤️ collect(toList()) возвращает ArrayList, который с радостью принимает новые элементы. 💔 toList() возвращает неизменяемый список. Вызов addAll выбросит исключение. Это очевидная ошибка JDK. Коллекторы - часть Stream API. В рамках функционального стиля логично возвращать неизменяемую коллекцию. Плюс ошибка на уровне проектирования - неизменяемые коллекции должны иметь интерфейс без методов add/set/remove. Пользователь не должен запоминать эти нюансы и получать внезапные ошибки в рантайме. Побухтеть на JDK - дело святое, но сфокусируемся на другом. Для начала опишу историю целиком: Человек поправил toList в коде фичи А. Там не было addAll, и все отлично работало. Затем человек сделал автозамену по всему сервису. Изменение с виду абсолютно безобидное, подвоха никто не ждал. К счастью, код был хорошо покрыт тестами. Некоторые упали, отсюда мы и узнали эту милую особенность JDK💅 Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд. Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом. Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️

Как изменится результат выполнения кода выше, если заменить collect(toList()) на toList()?
Anonymous voting