Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام Java: fill the gaps
تُعد قناة Java: fill the gaps (@java_fillthegaps) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 12 549 مشتركاً، محتلاً المرتبة 10 121 في فئة التكنولوجيات والتطبيقات والمرتبة 52 862 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 12 549 مشتركاً.
بحسب آخر البيانات بتاريخ 07 يونيو, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -46، وفي آخر 24 ساعة بمقدار 0، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 34.72%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً N/A% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 0 مشاهدة. وخلال اليوم الأول يجمع عادةً 0 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 0.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل redis, hashmap, linkedhashmap, индекс, фича.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 08 يونيو, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.
List list = new ArrayList(25);Без лишнего копирования и переносов разница в скорости становится ошеломительной — список заполняется на 20-80% быстрее! Также ArrayList однозначно победил в номинациях: 🔹 Сортировка 🔹 Обход через цикл for 🔹 Простые Stream API 🔹 Любые Stream API с опцией parallel() В сложных однопоточных Stream API большая часть вычислений идёт на что-то полезное, и влияние оверхеда снижается. Ожидаемо:) Ещё факты против LinkedList: 🔸 Большинство классов JDK используют ArrayList для внутренних задач 🔸 Joshua Bloch (автор класса и книжки Effective Java) в 2015 написал твит:
"Does anyone actually use LinkedList? I wrote it, and I never use it."Но аналоги LinkedList иногда встречаются. В Scala для неизменяемого списка за основу взят именно двусвязный. Возможно, это лучше для каких-то сценариев, но пока не знаю для каких. На картинке часть моих бенчмарков. N — количество элементов в списке. Зелёный цвет обозначает победителя в категории. Ярко-зелёный — разница значений более 50%. ❗️Результаты на разных железках могут отличаться ❗️
Object[] elementData;Размер массива задаётся в конструкторе:
new ArrayList(50). Значение по умолчанию — пустой массив.
Структура LinkedList чуть сложнее. Каждый элемент оборачивается в класс:
private static class Node {
E item;
Node next;
Node prev;
}
Т.е сам элемент списка + указатели на следующий и предыдущий элемент.
В объекте LinkedList хранится ссылка на первый и последний элемент списка:
Node first; Node last;2. Доступ по индексу: list.get(i) или list.set(i) ArrayList просто обращается по индексу массива LinkedList идёт долгим путём. Берёт элемент first идёт по ссылкам, пока не дойдёт до i-го элемента. Затем либо возвращает значение, либо обновляет. Кажется, что второй подход гораздо дольше. И это правда🙂 Чтобы получить 5000-ый элемент в списке из 10к элементов ArrayList тратит 6 наносекунд, а LinkedList — 8750. Чем больше элементов, тем больше разница. 3. Вставка в середину списка ArrayList Допустим, для списка 🟧🟧🟧🟧 вызвали метод add(2, 🦄) Создаётся новый массив размером +1: ⬜️⬜️⬜️⬜️⬜️ Все элементы старого списка копируются туда так, чтобы образовалось свободное место для нового элемента: 🟧🟧⬜️🟧🟧 Обновляем элемент: 🟧🟧🦄🟧🟧 LinkedList Рассмотрим тот же метод add(2, 🦄): ▫️ Создаём элемент списка с двумя ссылками: ⬅️🦄➡️ ▫️ Идём до текущего элемента 2 и получаем ссылки на элементы 1 и 3 ▫️ У элемента 1 обновляем ссылку на next, у 3 — на prev Само добавление простое, но перед ним нужно пройтись по списку. Это долго, поэтому LinkedList и здесь проигрывает по скорости. 4. Заполнение списка (см код в вопросе перед постом) ArrayList ▫️ Создаётся пустой массив ▫️ При первом add размер увеличивается до 10 ▫️ При добавлении 11 элемента создаётся новый массив размером 15. Предыдущие элементы копируются, затем добавляется новый ▫️ И так далее: когда места не хватает, создаётся массив в 1.5 раза больше LinkedList У текущего tail элемента обновляется ссылка next. Новый объект записывается как tail. Что работает быстрее? Интуиция подсказывает, что LinkedList, так как для ArrayList нужно часто переносить элементы в новый массив. На самом деле операции копирования выполняются быстро, так как элементы лежат в памяти рядом, а у процессоров хорошая поддержка этой операции. Бенчмарки показывают, что до 100к элементов разницы нет, а потом побеждает ArrayList. А при миллионе элементов ArrayList копируется реже и в итоге заполняется в два раза быстрее. Внизу таблица JMH бенчмарков с моего компьютера. На других железках результаты могут отличаться. Ответ на вопрос перед постом: для 50к элементов время почти одинаковое.
ArrayList быстрый доступ по индексу, а в LinkedList легко вставлять элементы в середину списка. Получаешь оффер🙂
Но это немного скучно, поэтому решила описать более жизненные кейсы и подробнее написать про строение. И самое главное — измерить время работы операций!
🔸 Часть 1: вводная. Строение списков и основные операции
🔸 Часть 2: жизненная. Что и когда использовать в кодеpostProcessBeforeInitialization
— postProcessAfterInitialization
🔸 ApplicationContext — главный объект, хранит и вызывает все штучки выше
Теория
Что происходит и как туда внедриться:
1️⃣ Считывается информация из xml, yaml файлов
Создаётся список BeanDefinition. Интерфейс — BeanDefinitionReader
2️⃣ Обработка BeanDefinition
Дополняются параметры, определяется, кто от кого зависит и в каком порядке создавать бины. Интерфейс — BeanFactoryPostProcessor
3️⃣ Создаются экземпляры бинов
Сначала создаются объекты @Component, потом @Bean (из классов-конфигураций). Для каждого бина порядок такой:
▫️ Вызов конструктора
▫️ Проставляются @Autowired зависимости над полями
▫️ Вызов set методов с @Autowired
▫️ Вызов методов интерфейсов *Aware
▫️ Все BPP, метод postProcessBeforeInitialization
▫️ Метод с аннотацией @PostConstruct
▫️ Метод afterPropertiesSet (если класс реализует интерфейс InitializingBean)
▫️ Метод, определенный в @Bean(initMethod = "xxx")
▫️ Все BPP, метод postProcessAfterInitialization. Тут обычно создаются прокси-классы
▫️ Бин добавляется в контейнер
4️⃣ Событие “контекст обновился”
Ловится через реализацию интерфейса ApplicationListener<ContextRefreshedEvent>
Или через метод с аннотацией
@EventListener public void xxx(ContextRefreshedEvent contextRefreshedEvent)5️⃣ Запускается сервер 6️⃣ Событие "приложение запущено" Ловим событие
ApplicationStartedEvent как в пункте 5
7️⃣ Логика в методе main, если у вас Spring Boot
Менее популярные точки расширения:
▪️ Реализовать интерфейс *Aware
▪️ Поймать другие Spring Boot и Spring Core события
Практика
Точек расширения много, но что обычно используется:
🔸 Создать прокси-класс — BeanPostProcessor, метод after
🔸 Выполнить какую-то логику ДО старта сервера — поймать событие ContextRefreshedEvent
🔸 ПОСЛЕ старта сервера — событие ApplicationStartedEvent
Часто разогрев кэша, service discovery и подобные задачи помещают в @PostConstruct, но на момент вызова не все бины могут быть готовы. При работе с событиями этой проблемы нет.
И ещё пара рекомендаций:
🔹 Использовать одинаковые механизмы для одинаковых задач
🔹 Если в проекте больше трёх процессоров или задач на старте, упростите наблюдение за магией спринга. Это может быть файлик с описанием всех процессоров, очерёдностью и важными деталями. Или все процессоры поместить в одну папку.
🔹 Не помещайте бин процессоры и прослушивание событий в классы с бизнес-логикой. Нарушается принцип единственной ответственности, также могут быть проблемы с порядком обработки или количеством вызовов.
———
И небольшой апдейт по курсу: продлила скидос до 11 марта!public void doWork() {
// начать транзакцию
super.doWork();
// закончить транзакцию
}
🔸 Создаст компонент с пулом транзакций и несколько промежуточных объектов.
✅ Минимум усилий от разработчика
❌ Сложно исправлять проблемы
❌ Большие накладные расходы. Spring создаёт и связывает сотни объектов на старте приложения, поэтому долго стартует и занимает много памяти
3️⃣ Кастомизация
Можно написать свои аннотации или переопределить стандартное поведение. Не могу сказать, что это нужно каждый день, но раз в полгода-год появляются такие задачи. У спринга можно встроиться почти куда угодно. Это мы подробно обсудим в следующем посте.
✅ Легко встроиться в процесс
❌ Из-за этого сам процесс усложняется
Именно комбо 1, 2 и 3 делает Spring таким популярным. Другие фреймворки предоставляют 1-2 пункта, но все три — только Spring.
Самые популярные модули — MVC, Security, Data и Cloud. У каждого из них под капотом целый мир, и об этом тоже как-нибудь напишу.extends
▫️ В имени часто содержится Abstract, Template, Base
▫️ Класс реализует не больше одного абстрактного класса
Интерфейс:
▫️ Ключевое слово implements
▫️ Класс может реализовать несколько интерфейсов
▫️ 5 лет назад интерфейсам было модно добавлять суффикс able: Iterable, Comparable. Норма сегодняшнего дня — называть интерфейс по тем же правилам, что и класс.
💫 Назначение
Абстрактный класс — шаблон класса. Вспомогательная структура, чтобы не дублировать код в классах одной иерархии.
Интерфейс описывает методы для верхнеуровнего взаимодействия с классом, модулем или системой.
⭐️ Репутация
Абстрактный класс сокращает объем необходимого кода, когда иерархия классов конечна или известна заранее. Большое количество абстрактных классов считается анти-паттерном, у такой системы плохая читаемость, и в неё сложно вносить изменения.
Интерфейс используется в большинстве паттернов GoF и принципах SOLID. Поддерживает инкапсуляцию.interface Adapter {
default int get() {…}
}
Класс, который реализует интерфейс, переопределяет такой метод при необходимости.
❓Зачем?
1️⃣ Облегчить изменения API
Во времена java 7 в интерфейсах были только определения методов:
interface Collection<Т> {
void add(Т);
Т get();
}
Чтобы добавить, удалить или поменять сигнатуру метода нужно одновременно поменять код и в интерфейсе, и во всех реализациях. Если всё в рамках одного проекта, то проблем нет. Но если мы пишем библиотеку, то задача усложняется. Пользователи могут столкнутся с проблемами совместимости при переходе на новую версию.
Задача дефолтных методов — сгладить этот процесс, предоставить приемлемую или временную альтернативу. В JDK основная цель дефолтных методов — поддержка Stream API в коллекциях.
2️⃣ Вспомогательные методы
Которые не входят в прямую функциональность интерфейса, но их удобно добавить сюда, а не в каждый класс реализации. Нарушается принцип Interface segregation и часто похоже на костыль. Не одобряю, но такое встречается🙂
3️⃣ Комбинации базовых методов
Мы обсуждали это в прошлом посте. В интерфейсе есть методы, которые по сути — комбинация других методов этого же интерфейса.
Для таких комбинаций можно сделать статический метод. Он вызываются через имя интерфейса и недоступен у экземпляров. Если это неудобно, метод можно сделать как метод по умолчанию.
Пример: интерфейс Comparator
Цепочка из 2 компараторов — это 2 вызова compare и объединение результатов. Логика всегда одинакова, и нет смысла дублировать её в каждом подклассе:
default Comparator thenComparing(Comparator) {…};
❓Что если класс реализует 2 интерфейса с методами по умолчанию?
Правила такие:
🔸 Если в классе переопределён метод по умолчанию — используется метод класса
🔸 Если один интерфейс наследуется от другого — используется метод наследника
🔸 В остальных случаях — ошибка компиляции
Отсюда ответы на вопросы перед постом:
1️⃣ Ошибка компиляции
2️⃣ Напечатается В
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
