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، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -46 و در ۲۴ ساعت گذشته برابر 0 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 34.72% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً 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️⃣ Напечатается В
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
