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 552 suscriptores, ocupando la posición 10 101 en la categoría Tecnologías y Aplicaciones y el puesto 52 755 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 552 suscriptores.
Según los últimos datos del 05 junio, 2026, el canal mantiene una actividad estable. En los últimos 30 días la variación de miembros fue de -49, y en las últimas 24 horas de -4, 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.71%. 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 07 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.
String str = "Hello, " + name + "!";Сюда же относится StringBuilder, метод concat и тд. 🔸 Интерполяция — замена переменных внутри шаблона:
String name = "Jake";
String str = "Hello, ${name}!";
В чистом виде в java такого нет. В Formatter и MessageFormat вместо переменных какие-то %s и %d, а переменные стоят отдельно:
String.format("%d plus %d equals %d", x, y, x + y);
Для сравнения, как это выглядит в Kotlin:
"$x plus $y equals ${x + y}"
Так вот, в java 21 появится интерполяция!
В начале строки нужно добавить STR., а переменные поместить в \{}
int x = 10, y = 20;
String str = STR."\{x} + \{y} = \{x + y}";
// "10 + 20 = 30"
Хорошо работает вместе с текстовыми блоками (многострочные строки в тройных кавычках):
String name = "Joan";
String phone = "555-123";
String json = STR."""
{
"name": "\{name}",
"phone":"\{phone}",
}
""";
Внутри можно вызывать методы и писать блоки кода:
String time = STR."The time is \{
DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(LocalTime.now())
} right now";
//"The time is 09:01:45 right now"
Читаемость текста снижается, но если очень хочется — почему нет.
❓ Зачем нужен префикс STR? Почему нельзя просто добавить новый функционал в строки?
Здесь 2 причины:
1️⃣ Для обратной совместимости
На джаве написано много кода, и наверняка какие-то строки содержат блоки /{}. Будет обидно, если этот код перестанет компилироваться. Поэтому строки для интерполяции нужно явно обозначить
2️⃣ Может быть не только STR😱
Здесь открывается портал в другой мир. По задумке авторов темплейты могут подставлять переменные, валидировать данные и делать преобразования. Например, так:
JSONObject json = JSON."{ id: /{id}}";
или даже так:
ResultSet rs = DB."SELECT * FROM Person WHERE name = \{name}";
Для запросов в БД это, наверное, слишком, а вот для работы с JSON выглядит очень удобно.
Но рано радоваться🙂 Из коробки этого не будет, только набор классов для кастомизации. Будем надеяться, что авторы библиотек возьмут фичу на вооружение.
❓ Как это работает? Выглядит как магия!
В JDK появится статическое поле StringProcessor STR, а строка
String str = STR."/{name}!";
во время компиляции превратится в
StringTemplate template = new StringTemplate(паттерн, параметры);
String str = STR.process(template);
Cинтаксический сахарок и никакого волшебства✨Evaluate and log
🔸Вписать нужное выражение
Отладчик не будет останавливать выполнение, а запишет в консоль значение выражения.
Незаменимая фича для отладки многопоточных приложений, кода сторонних библиотек и удалённого дебага!
3️⃣ Отключение брейкпойнта
Ненужный брейкпойнт можно не удалять, а отключить:
▪️Щёлкнуть колёсиком по брейкпойнту
ИЛИ
▪️Правый щёлчок по брейкпойнту → снять галочку с Enabled
4️⃣ Массовое удаление
Когда в проекте много брейкпойнтов, IDE при дебаге немного тормозит. Чтобы удалить ненужные, откройте полный список:
▫️Правый клик по любому брейкпойнту
▫️Ссылка More
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
В этом окне ещё есть фильтры для выборочной остановки, но удобнее пользоваться условиями из первого пункта.@Test.
Через аннотацию @DisplayName задаётся симпатичное имя теста в отчёте.
Чтобы выполнить что-то до или после выполнения теста, используются методы с аннотациями:
▫️ @Before, @BeforeAll ▫️ @After, @AfterAllJUnit создаёт новый экземпляр класса на каждый тестовый метод. Класс
ServiceTest с пятью методами @Test во время запуска превратится в 5 экземпляров класса ServiceTest.
Благодаря этому тесты выполняются независимо.
Этим JUnit отличается от TestNG, где создаётся один экземпляр класса на все тестовые методы. Если хочется как в TestNG, добавьте над классом аннотацию @TestInstance(Lifecycle.PER_CLASS)
2️⃣ Проверки
Сердце каждого теста - методы с приставкой assert*:
🔸 assertTrue 🔸 assertEquals 🔸 assertInstanceOfВ самом JUnit мало методов, более удобные ассерты есть в библиотеках Hamсrest и AssertJ. AssertJ, на мой взгляд, более читабельный, но Hamсrest используется чаще. 3️⃣ Группировка тестов Аннотация
@Tag("groupName") объединяет тесты в группы. Работает и для одного теста, и для класса.
Можно указывать тэги в системе сборки и при запуске тестов из IDE.
4️⃣ Отключение тестов
Аннотация @Disabled. Есть продвинутые варианты, можно отключить тесты для
▫️ операционной системы
@DisabledOnOs(WINDOWS)▫️ версии java
@DisabledOnJre(JAVA_9) @DisabledForJreRange(min = JAVA_9)▫️ системных переменных:
@DisabledIfSystemProperty(named = "ci-server", matches = "true") @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")5️⃣ Параметризированные тесты Помогают запустить один тест с разными аргументами. Выглядит так:
@ParameterizedTest
@ValueSource(ints={100,-14})
public void test(int input) {}
Такой тест запустится дважды - с аргументом 100 и -14.
Вместо готового списка можно брать значения
🔸 из CSV файла @CsvSource
🔸 из метода @MethodSource
6️⃣ Проверка таймаута
▫️ Через ассерт
assertTimeout(ofMinutes(2), ()->{});
▫️ Через аннотацию
@Timeout(value=42,unit=SECONDS)7️⃣ Полезные библиотеки ▫️ Hamсrest, AssertJ — расширенные библиотеки методов-ассертов ▫️ Mockito для заглушек. Добавляете библиотеку в pom.xml или build.gradle, а в тест - аннотацию
@ExtendWith(MockitoExtension.class)
▫️ Testcontainers для запуска внешних компонентов в докере. Добавляем библиотеку, аннотацию @Testcontainers над классом и @Container над компонентом
▫️ Java Faker — генератор данных для тестов
Ещё я когда-то писала 2 хороших поста на тему, чем отличается JUnit 4 от Unit 5. Если вас удивляет, почему там разные аннотации и почему версии не совместимы друг с другом, то почитайте:)new HashMap<>(1000). Сначала расскажу, почему этот ответ не походит. Потом наглядно покажу нарушение инкапсуляции, и что за костыль добавили в Java 20.
Итак, что происходит внутри HashMap?
Ячейки хэш-таблицы (или бакеты) хранятся в переменной Node[] table как простой массив.
При вызове new HashMap() без параметров создаётся 16 бакетов. Если передать параметр с размером, то берётся ближайшая степень двойки.
new HashMap(1000) создаёт массив из 1024 элементов.
Кажется, что всё в порядке, но мы забыли про ребалансировку😒
HashMap хорошо работает, когда в каждом бакете 0 или 1 элемент. Тогда скорость поиска и добавления будет той самой О(1).
Когда элементов становится больше, растёт шанс, что в один бакет попадёт несколько элементов. Поэтому в определённый момент HashMap удваивает количество бакетов и перераспределяет элементы. За момент, когда пора начать эту операцию, отвечает поле threshold.
Его первое значение считается как [планируемое число элементов * 0.75], т.е когда HashMap заполнен на 3/4. При удвоении числа бакетов threshold тоже удваивается.
И смотрите, что получается:
🔹 Мы хотим добавить в мэп 1000 элементов и вызываем конструктор с подходящим параметром initialCapacity:
new HashMap(1000);
🔹 Создаётся 1024 бакета (ближайшая степень двойки)
🔹 Рассчитывается threshold: 1024*0,75 = 768
🔹 Добавляется 768 элементов
🔹 Приходит 769 элемент, начинается ребалансировка:
▫️ количество бакетов удваивается, теперь их 2048
▫️ текущие элементы распределяются между ними
▫️ новый threshold удваивается, теперь это 1538
Что получилось: мы пообещали добавить в HashMap 1000 элементов. Сдержали обещание, но перестройка мэп всё равно произошла.
Чтобы HashMap работал оптимально, нужно учесть ребалансировку и передать в конструктор, например, 1500. Надо знать детали реализации, чтобы получить то, что хотим.
И это образцовое нарушение инкапсуляции🤌
В java 20 в HashMap добавили костыльный метод, который исправляет ситуацию:
HashMap.newHashMap(1000);
Внутри произойдёт вычисление 1000 / 0.75 = 1334, в итоге создаётся 2048 бакетов.
Почему это костыль? Потому что исходная проблема не решается. Жизнь пользователя не становится легче, ему нужно запомнить "чтобы задать размер мэп — не пользуйся конструктором, пользуйся специальным методом".
Хороший API — понятный, удобный и дружелюбный. Пользователю легко выбрать нужный метод, все параметры хорошо описаны в документации. Когнитивная нагрузка при использовании минимальна, нет подводных камней и обходных путей. Для собеседований сложно придумать вопрос с подвохом🙂
Важные заметки:
🔸 В JDK много образцового кода, и я рекомендую изучать исходники как можно чаще. HashMap — неприятное исключение
🔸 В ConcurrentHashMap всё хорошо. new ConcurrentHashMap(1000) сразу создаёт 2048 бакетов и не занимается лишними балансировками[название книги] summaryХорошие конспекты по DDIA: покороче и подлиннее. Как прочитать книгу, в которой больше 10 страниц Большой объём часто вгоняет в тоску. Что может здесь помочь: 🔹 Читать в группе Объединиться с коллегами или друзьями и установить распорядок. Например, выбираете главу, которую надо прочитать за неделю, в пятницу созваниваетесь и обсуждаете прочитанное. 🔹 Присоединиться к читальному клубу Я знаю только один, сейчас они читают Art of Multiprocessor Programming и Effective Java. В крупных компаниях организуют похожие клубы, это надо узнать у HR. 🔹 Маленькие шаги Поставьте себе выполнимый план и придерживайтесь его. Допустим, каждый день читать по 10 страниц или по 20 минут. Важно удобно встроить чтение в вашу жизнь. Например, если после работы нет сил, то попробуйте читать до работы. 🔹 Не читать целиком Прочитать только интересную главу или пропускать главы, которые кажутся очевидными. Но если чувствуете высокий уровень книги, лучше прочитать полностью. 🔹 Не читать🙂 Если автор нудный или чтение идёт тяжело, можно взять темы из содержания и последовательно искать их на Youtube или в гугле. Не для всех книг подойдёт, но для некоторых норм. Что советуешь прочитать? Универсальных рекомендаций нет, всё зависит от текущих задач и ваших интересов. Джуниорам полезно прочитать Effective Java. Хочется погрузиться в недра БД — Database Internals. Активно следите за продом — Site Reliability Engeneering от гугла. Прочитали DDIA и хотите продолжения — Designing Distributed System. Разобраться в операционных системах или сетях — Таненбаум. Это я сама себе посоветую, а надо ли вам это читать — не знаю:) Спросите у старших коллег, они наверняка подскажут что-то релевантное вашему опыту и задачам проекта.
pg_stat_statements — там собирается статистика по запросам. Чтобы получить достоверные данные, берём статистику с продакшн базы.
Ищем запросы, которые выполняются часто или долго.
Шаг 2. Работаем с конкретным запросом
Для экспериментов берём тестовую базу с большим количеством данных. Минимум миллион записей, иначе эффект оптимизаций не будет заметен.
Прогоняем запрос через EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT * FROM users where name = ’K’;EXPLAIN пишет только план выполнения запроса. EXPLAIN ANALYZE выполняет запрос и показывает ▪️ planning time — время планирования запроса ▪️ execution time — время выполнения запроса. Работаем с этим значением Можно поиграть с условиями, порядком соединения таблиц и разными функциями. Обратите внимание на способ обхода таблицы:
Index Scan using name_index on — при выполнении запроса используется индекс, и это отлично
Seq Scan on означает, что происходит долгий последовательный обход таблицы. Причиной может быть
🔸 поиск по условию (where name = …)
🔸 проверка уникальности поля
🔸 проверка внешнего ключа (foreign key)
Решение здесь простое — добавить индекс по проблемному полю. Базовый вариант выглядит так:
CREATE INDEX index_name ON users(name);Дальше всё просто: ▫️ Запустить
EXPLAIN ANALYZE
▫️ Увидеть в плане выполнения новый индекс
▫️ Порадоваться снижению execution time
Для оптимизаций популярных и тяжёлых запросов добавление индекса оправдано. Разумеется, не нужно добавлять индексы для всех запросов и всех условий. Индексы занимают много места и замедляют запись в базу.
В оптимизации запросов огромное количество нюансов, но большинство проблем решается кэшем и добавлением индекса. Более сложные случаи лучше обсуждать с коллегами DBA😌List users = … users.forEach(u -> session.save(u));Тогда в хэшкоде можно спокойно использовать id и для уже сохранённых объектов хэшсет будет работать как надо:
Set users = …
if (!users.contains(…)) {…}
Итого:
🔸 Hashcode нужен только, когда структура используется в hash-based структурах. Если новые объекты не складываются в HashSet или HashMap, то проблемы вообще нет
🔸 Если вы хотите возвращать в хэшкод константу, рассмотрите вариант хранения сущностей в ArrayList или TreeSet
Ответ на вопрос перед постом: зависит от сценариев использования. Если новые объекты User собираются в коллекцию, я бы складывала в список, а hashcode реализовала как return id; Но ситуации бывают разные, решение не универсально.
И более глобальные выводы:
Хороших материалов по разработке мало. Но даже в хороших легко свернуть не туда. Статья Thorben Janssen в целом ок, но итог немного сбивает с толку. Сравните:
💁🏼♂️ "Если для сущности id генерируется в БД, hashcode должен возвращать константу"
💁🏼 "Если новые Hibernate сущности складываются в hash структуры, и у них нет final полей, то для соблюдения контракта можно использовать в hashcode константу"
Второй вариант корректнее, но первый проще и лучше запоминается.
Не попадайте в эту ловушку. Задача разработчика — разобраться в сценариях, оценить варианты и найти подходящий😌
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
