Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
Mostrar más📈 Análisis del canal de Telegram Java: fill the gaps
El canal Java: fill the gaps (@java_fillthegaps) en el segmento lingüístico de Ruso es un actor destacado. Actualmente la comunidad reúne a 12 549 suscriptores, ocupando la posición 10 121 en la categoría Tecnologías y Aplicaciones y el puesto 52 862 en la región Rusia.
📊 Métricas de audiencia y dinámica
Desde su creación el невідомо, el proyecto ha mostrado un crecimiento acelerado, reuniendo a 12 549 suscriptores.
Según los últimos datos del 07 junio, 2026, el canal mantiene una actividad estable. En los últimos 30 días la variación de miembros fue de -46, y en las últimas 24 horas de 0, conservando un alto alcance.
- Estado de verificación: No verificado
- Tasa de interacción (ER): El promedio de interacción de la audiencia es 34.72%. Durante las primeras 24 horas tras publicar, el contenido suele obtener N/A% de reacciones respecto al total de suscriptores.
- Alcance de las publicaciones: Cada publicación recibe en promedio 0 visualizaciones. En el primer día suele acumular 0 visualizaciones.
- Reacciones e interacción: La audiencia responde de forma activa: el promedio de reacciones por publicación es 0.
- Intereses temáticos: El contenido se centra en temas clave como redis, hashmap, linkedhashmap, индекс, фича.
📝 Descripción y política de contenido
El autor describe el recurso como un espacio para expresar opiniones subjetivas:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
Gracias a la alta frecuencia de actualizaciones (últimos datos recibidos el 08 junio, 2026), el canal mantiene la vigencia y un amplio alcance. La analítica demuestra que la audiencia interactúa activamente con el contenido, lo que lo convierte en un punto de referencia dentro de la categoría Tecnologías y Aplicaciones.
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️⃣ Напечатается В
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
