Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
نمایش بیشتر📈 تحلیل کانال تلگرام Java: fill the gaps
کانال Java: fill the gaps (@java_fillthegaps) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 12 561 مشترک است و جایگاه 10 092 را در دسته فناوری و برنامهها و رتبه 52 768 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 12 561 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 03 ژوئن, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -39 و در ۲۴ ساعت گذشته برابر -3 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 34.68% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً N/A% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 0 بازدید دریافت میکند. در اولین روز معمولاً 0 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 0 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند redis, hashmap, linkedhashmap, индекс, фича تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 04 ژوئن, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
CREATE INDEX CONCURRENTLY idx ON t(f);
Если всё так круто, почему опция не выбрана по умолчанию?
Потому что появляются новые проблемы.
Обычный индекс один раз проходит по таблице, которая не меняется, поэтому успех неизбежен.
Если индекс строится во время активной работы с базой — привет гонки, параллельные транзакции и вся классика многопоточных проблем. В итоге
▫️ Индекс строится гораздо дольше. Скан таблицы происходит 2 раза, индекс постоянно ждёт завершения транзакций и борется с внутренними противоречиями
▫️ Индекс может не получиться и остаться в статусе invalid. В таких случаях надо снести неполучившийся индекс и начать построение заново. Возможно не один раз💔
Второй важный момент, который касается неблокирующих индексов — партиционированные таблицы.
Если добавить обычный индекс для "основной" таблицы, для текущих и будущих(!) партиций индекс создаётся автоматически. Очень удобно
Но для CONCURRENTLY индекса такая схема не работает. Чтобы создать неблокирующие индексы для партиций придётся делать так:
▫️ Создать индекс на "основную" таблицу
▫️ Создать неблокирующий индекс для каждой партиции
▫️ Присоединить эти индексы к "основному"
Примерно так:
CREATE INDEX idx ON ONLY t(f);
CREATE INDEX CONCURRENTLY p1_idx ON p1_t(f);
ALTER INDEX idx ATTACH PARTITION p1_idx;
В общем, при создании индекса для большой таблицы придётся делать выбор:
🪑 CREATE INDEX и заблокировать работу с таблицей на десятки минут
🪑 CREATE INDEX CONCURRENTLY и выполнить кучу дополнительной работы, но меньше затронуть пользователя
Универсального решения нет, выбираем стул в зависимости от ситуации:)spring.task.scheduling.pool.size=5Задачки будут летать параллельно и не блокировать друг друга👯♀️ 🚨Ситуация 2: у сервиса несколько экземпляров а задача по расписанию должна выполниться один раз. Какие решения я видела: 🕳 Отдельный сервис для скедьюлд задач 🕳 Синхронизация через базу или ShedLock, чтобы решить, какой сервис выполнит задачу. На одном проекте для этой цели использовался leader election в Zookeeper🙈 Всё это интересно реализовать и вписать красивую строчку в резюме, но есть способ проще. Смысл в том, чтобы разделить полезную работу и вызов по расписанию. Например: ▫️ Делаем в сервисе нужный метод ▫️ Добавляем контроллер, который его вызывает ▫️ Внешний компонент следит за расписанием и дергает контроллер Кто этот внешний компонент? Кто угодно, в инфраструктуре полно инструментов для регулярных задач. Начиная от CronJobs в Kubernetes до баш скрипта с crontab. Проблемы со Scheduled задачами часто проявляются не сразу и не явно. Если миграция данных в другую систему запустилась слишком поздно, данные в разных системах разойдутся. А если задача вообще не выполнилась? Я такое разгребала неоднократно, поэтому совет от души: когда добавляете задачу по расписанию, учтите 2 момента из этого поста. Сэкономите проекту десятки человекодней🔥
public class HashSet implements Set {
HashMap map;
public HashSet() {
map = new HashMap<>();
}
...
}
Прошло 4 года. В java 1.4 добавились Linked* реализации:
▫️ LinkedHashMap стал наследником HashMap. Тут всё сложилось удачно
▫️ LinkedHashMap стал наследником HashSet. И здесь не всё гладко.
У HashSet внутри HashMap, доступа снаружи к нему нет. А внутри LinkedHashSet должен быть LinkedHashMap. Как заменить объект внутри родителя без изменения существующих методов?
Решение в итоге ужасное. В HashSet добавили такой package private конструктор:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
поле dummy нужно, чтобы этот конструктор отличался от уже существующих.
Только вдумайтесь: в родителя добавили специальный конструктор для конкретного потомка. С его деталями реализации!
Код получился бы чище, если вместо наследования от хэшсета использовать композицию:
class LinkedHashSet implements Set {
private LinkedHashMap map;
public LinkedHashSet() {
this.map = new LinkedHashMap<>();
}
...
}
✅ В HashSet нет лишнего
😑 Нужно скопировать кучу мелких методов типа add, size, contains
На примере LinkedHashSet чётко видна проблема наследования.
У родителя по всем заветам инкапсуляции скрыта реализация. Если наследование изначально не предусмотрено, к внутренним полям родителя нет прямого доступа. Код сложнее переиспользовать, и появляются странные конструкции.
Даже если дети спланированы заранее, зависимость от родителей ограничивает их развитие. Детям приходится подстраиваться и выбирать не самые оптимальные для себя решения.
(этот абзац всё ещё про разработку)
Композиция и в моменте, и в перспективе гораздо удобнее. Но 30 лет назад это было не так очевидно.LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);
map.put(2, 2);
int lastKey = map.sequencedKeySet().getLast(); // 2
Если в конструкторе передать accessOrder=true, список запоминает порядок извлечения элементов. Вызов get(1) отправляет 1 в конец списка:
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.8f, true);
map.put(1, 1);
map.put(2, 2);
map.get(1);
int lastKey = map.sequencedKeySet().getLast(); // 1
С теорией закончили, переходим к интересному.
В первую очередь, бросается в глаза флажок в конструкторе. Гораздо симпатичнее в параметрах выглядел бы enum. Что-то вроде ELEMENT_ORDER.INSERT. Подробно бэд пректис с флажками и альтернативы разбирала тут.
Второй момент касается проектирования. Зачем вообще нужен LinkedHashMap с accessOrder=true?
Документация пишет, что это отличная база для LRU кэша. С первого взгляда похоже на правду, но есть пара нюансов:
▫️ У LinkedHashMap нет ограничений на размер. У кэша — есть
▫️ С кэшами работают много потоков. LinkedHashMap - не потокобезопасен, единственный вариант для корректной работы — synchronized обёртка:
Map map = Collections.synchronizedMap(new LinkedHashMap(…))Получится кэш с пропускной способностью в один поток😐 В итоге: ❌ В чистом виде LinkedHashMap с accessOrder=true нужен либо никому, либо в редких случаях ❌ В качестве LRU кэша (как предлагается в документации) класс использовать сразу не получится. Либо доделывать, либо взять уже готовые и более эффективные реализации кэша. Подобные опции в API - лишние. Хорошие библиотеки и фреймворки состоят из двух частей: базовые многофункциональные элементы + удобные методы для популярных кейсов. Так достигается баланс между простотой и гибкостью. ✨ Хороший пример: экзекьюторы ✨ Базовый элемент для экзекьютора — класс ThreadPoolExecutor. В конструкторе 5 параметров, можно переопределить методы. Есть готовые варианты, которые подойдут для большинства задач:
Executors.newFixedThreadPool(int nThreads) Executors.newSingleThreadExecutor() Executors.newCachedThreadPool()Всё вместе - приятное и удобное апи. Примеры выше простые, но сам подход универсален. Комбо базовых и удобных методов отлично подходит для библиотек интеграции или для общих модулей. Ответ на вопрос перед постом: с флажком true запоминается порядок извлечения элементов. Подписчики канала - умнички независимо от каких-то флажков🥰
CREATE TABLE tweets (
id BIGINT ... PRIMARY KEY,
text VARCHAR(255) NOT NULL,
parent_id BIGINT REFERENCES tweets(id)
);
Код сохранения твита примерно такой:
public Tweet saveTweet(Tweet tweet) {
// проверяем, что родитель существует
if (tweet.getParentId() != null) {
boolean parentExists = tweetRepository.existsById(tweet.getParentId());
if (!parentExists) {
throw new IllegalArgumentException("Parent not exists");
}
}
return tweetRepository.save(tweet);
}
Какую аннотацию ставим над методом saveTweet?PostConstruct или ловят событие ApplicationReadyEvent. Но у таких вариантов есть существенный минус.
Если "прогрев" находится в PostConstruct, как отключить его в тестах? Можно добавить флажок, сделать подкласс и заменить его в тестовом конфиге, но это не всегда помогает и часто выглядит как костыль.
Расскажу более аккуратный способ "прогреть кэши". Однажды точно пригодится💯
Смотрите, SpringApplication.run(...) возвращает полностью готовый контекст. Можно достать из него компонент и вызвать нужный метод "прогрева". В коде выглядит так:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MainApplication.class, args);
AccountService accService = ctx.getBean(AccountService.class);
accService.loadDictionary();
}
}
В чём плюс?
В интеграционных тестах с @SpringBootTest метод main не запускается. Соответственно, код внутри не выполняется. Нет костылей вокруг PostConstruct, всё чисто и красиво❤️🩹
Когда в тестах все же нужен "прогрев", добавляем параметр "использовать main метод":
@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.ALWAYS)
Итого. Если код должен выполниться после старта, но будет мешаться в тестах — пишите его в main. Очень полезный приём🔥class Employee extends Person {
private static int verifyAge(int value) {
if (age < 18)
throw new IllegalArgumentException(...);
return value;
}
Employee(int age) {
super(verifyAge(age));
}
}
С новым JEP эти костыли не нужны, нужные проверки пишем в начале конструктора:
class Employee extends Person {
Employee(int age) {
if (age < 18)
throw new IllegalArgumentException(...);
super(age);
}
}
Области кода вокруг this/super называются очень литературно: пролог и эпилог🥰
public Person {
// prologue
super();
// epilogue
}
В пролог нельзя вставить любой код:
❌ Нельзя обращаться к переменным родителя
❌ Нельзя вызывать нестатические методы
❌ Нельзя вызвать return
✅ Можно присвоить поля текущего класса
Особо не разгуляешься, всё же основной сценарий фичи — валидация входных параметров.
Ещё из интересного:
1️⃣ В JVM не пришлось ничего менять
Потому что правила "this обязательно первый" в JVM нет. Это ограничение только на уровне языка, чтобы упростить работу компилятора:)
2️⃣ Меняется ответ на частый собесный вопрос "в каком порядке инициализируются переменные". Раньше порядок для нестатических полей был такой:
Поля Parent - Конструктор Parent - Поля Child - Конструктор Child
В Java 25 поля наследника можно инициализировать ДО вызова конструктора родителя:
Employee(int age, String officeID) {
this.officeID = officeID;
super(age);
}
Общая схема с этими прологами-эпилогами очень усложняется.
3️⃣ Фича называется Flexible Constructor Bodies. Я не смогла придумать адекватный перевод, поэтому очень интересно, как её переведут в статьях-обзорах:)
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
