ar
Feedback
Java: fill the gaps

Java: fill the gaps

الذهاب إلى القناة على Telegram

Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat

إظهار المزيد

📈 نظرة تحليلية على قناة تيليجرام 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