Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
Show more📈 Analytical overview of Telegram channel Java: fill the gaps
Channel Java: fill the gaps (@java_fillthegaps) in the Russian language segment is an active participant. Currently, the community unites 12 552 subscribers, ranking 10 101 in the Technologies & Applications category and 52 755 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 12 552 subscribers.
According to the latest data from 05 June, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -49 over the last 30 days and by -4 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 34.71%. Within the first 24 hours after publication, content typically collects N/A% reactions from the total number of subscribers.
- Post reach: On average, each post receives 0 views. Within the first day, a publication typically gains 0 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 0.
- Thematic interests: Content is focused on key topics such as redis, hashmap, linkedhashmap, индекс, фича.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
Thanks to the high frequency of updates (latest data received on 07 June, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
s1.toLowerCase().equals(s2.toLowerCase()) s1.equalsIgnoreCase(s2) s1.compareToIgnoreCase(s2) != 0 s1.regionMatches(true, 0, s2, 0, s2.length())Шаг 2. Предположим возможные ситуации Строки могут сильно и слабо отличаться по регистру. Скорее всего, алгоритмы для пустых, коротких и длинных строк одни и те же, так что не будем различать эти случаи. Шаг 3. Углубляемся в реализацию 🔸 toLowerCase + equals ▫️ Для каждой строки создаёт копию в нижнем регистре. Затем включается обычный equals: ▫️ Сравнивает длины строк. Если не равны, сразу возвращается false ▫️ Сравнивает по одному символу, пока не дойдёт до конца строки или пока символы не будут разными ✖️В начале целиком обходим s1 и s2, чтобы создать копии в нижнем регистре. Когда длины строк отличаются (что можно узнать сразу), эта работа бесполезна ✖️ Если строки мало отличаются по регистру, то приведение всех символов к одному регистру будет лишним 🔸 compareToIgnoreCase cравнивает элементы по порядку. Если символы не равны — вызывает апперкейс и сравнивает ещё раз. ✔️ Uppercase происходит только при необходимости. Если разница в регистрах небольшая (s1=java, s2=Java), то этот подход будет быстрее ✖️ Цель метода — сравнить строки, поэтому нет быстрой проверки длины 🔸 regionMatches берёт символы из строк s1 и s2, сразу делает uppercase и сравнивает ✔️ Если строки по регистрам сильно отличаются, предварительный апперкейс ускорит проверку ✖️ Метод работает с подстроками произвольной длины, поэтому нет быстрой проверки длин 🔸 equalsIgnoreCase сравнивает длины строк, потом вызывает regionMatches Итог ▪️ Если строки разной длины, то однозначно побеждает equalsIgnoreCase ▪️ Если длины одинаковы и — регистры не сильно отличаются, то побеждает compareToIgnoreCase — нужно много преобразований — regionMatches equalsIgnoreCase для каждого символа делается апперкейс. Если строки по регистрам слабо отличаются, то это явно лишнее. Для таких случаев подойдёт кастомный метод:
if (s1.length() != s2.length()) {
return false;
}
return s1.compareToIgnoreCase(s2) != 0;
Лучшее из двух миров — быстрая проверка по длине и нет лишних апперкейсов.
Ниже — бенчмарки всех вариантов. Результаты на разных железках могут отличаться!stream().map(x → x.toString())🔸 Через Predicate передаётся условие фильтрации:
stream.filter(x → x > 5)🔸 Consumer используется в Stream API и библиотечных классах как терминальная операция:
list.forEach(e → System.out::println)🔸 Supplier ничего не принимает, но возвращает значение:
Stream.generate(() -> LocalTime.now())Function, Predicate и Concumer активно используются за пределами Stream API, а вот что делать с Supplier — не всем понятно. Зачем определять метод
void m(Supplier<List> list)вместо
void m(List list) ?
Популярные версии:
1️⃣ Кастомизация экземпляра. Передаём другой Supplier — возвращается другой экземпляр.
Для этой задачи проще указать интерфейс в параметрах
2️⃣ Реализация фабрики
Вариант из Effective Java, item 5. Источник авторитетный, но исходная цель Supplier здесь ускользает.
Во-первых, зачем передавать в параметры метода фабрику? Почему бы не создать экземпляр ранее и передать его?
Во-вторых, цель фабричного метода — упростить создание сложных объектов или отдавать разные объекты в зависимости от параметров. Supplier не содержит параметров и слабо подходит под эту задачу.
Цель Supplier — ленивая инициализация. Объект создаётся, когда он нужен, либо не создаётся вообще.
Игрушечный пример:
public Connection init(Supplier<Connection> connSupplier) {
// взять коннекшн из пула
return connSupplier.get();
}
Если выполнение не дойдёт до connSupplier, новый объект создан не будет. В варианте
public Connection init(Connection conn)для вызова метода нужно передать УЖЕ готовый экземпляр. Другой пример — метод Optional orElseGet(Supplier supplier). Новый объект создаётся только, если Optional пуст. Supplier участвует в сдвиге java в сторону функциональности. Function, Predicate и Concumer организуют функции высшего порядка, а Supplier — ленивые вычисления. Полезно в двух случаях: ✅ Когда объект может не пригодиться внутри метода ✅ Инициализировать объект нужно в момент вызова, не раньше. Например, у объекта в конструкторе есть LocalTime.now()
print(0.1 + 0.2) 0.3Почему python справился с примером, а java — нет? Как писать на java высоконагруженные приложения, если она не может сложить 0.1 и 0.2?😒 Oracle подробно объясняет этот феномен на 80 страницах. Главная проблема в том, как десятичная часть хранится в двоичном формате. Целые числа записываются через степень двойки однозначно:
9 = 8 + 1 = 2^3 + 2^0 → 1001Десятичная часть выражается через отрицательную степень двойки. Иногда получается нормально:
0.5 = 2^(-1) → 0.1Иногда не очень:
0.1 = 2^(-4) + 2^(-5) + 2^(-8) + … → 0.00111101110011001100110011001Если перевести это обратно в десятичную форму, видно, что хранится там совсем не 0.1, а 0.100000001490116119384765625 С 0.2 похожая ситуация, поэтому результат получается искажённым. Python 2.7 использует для вычислений ту же систему, но показывает меньше знаков после запятой. Поэтому ответ выглядит нормально. Python 3 выводит больше знаков и результат похож на результат java:
print(0.1 + 0.2) 0.30000000000000004❓Зачем использовать такую неточную систему? Если хранить целую и дробную часть одинаково, то для вычислений не нужно дополнительных преобразований. Результат считается быстро, а уровень погрешности на практике низкий. В нашем примере ошибка на 16 разрядов ниже основного значения, в большинстве случаев это ок. Для точных вычислений используются три основных метода: 🔸Ограниченная точность (limited-precision decimal) 🔸Символьная логика (symbolic calculations) 🔸Длинная арифметика (arbitrary-precision decimal) BigDecimal использует последний подход. Число 12.345 хранится как пара: ▫️целое значение: 12345 ▫️количество десятичных знаков: 3 За счёт этого BigDecimal хранит числа без потери точности. Целая часть хранится либо в переменной int, либо в массиве. Размер числа ограничен только количеством доступной памяти. Из минусов: ❌ Медленные вычисления ❌ Большой расход памяти ❌ Много промежуточных объектов ❌ Менее выразительный код Теперь ответ на второй вопрос перед постом: объекты
BigDecimal(0.2) и BigDecimal("0.2") НЕ равны.
В конструктор
BigDecimal(0.2)передаётся примитив double, в котором вместо 0.2 лежит 0.20000000000000001110223... Поэтому объект BigDecimal будет хранить это число, а не 0.2 Это самая частая ошибка при работе с BigDecimal. Для чисел с запятой надёжнее передавать в конструктор строку.
class IOException extends ExceptionЯвно указываются в сигнатуре методов:
void write(int c) throws IOExceptionКод с обработкой исключения обязателен, иначе программа не скомпилируется 🔸Unchecked исключения — наследники класса RuntimeException:
class NullPointerException extends RuntimeExceptionО них не пишут в сигнатуре методов и редко ловят в блоке try-catch. Компилятор не предупредит о возможных ошибках, но иногда о них предупреждает IDE. Оба типа можно поймать в блоке try-catch. Единственная техническая разница между checked и unchecked — обязательная обработка checked исключений. На уровне JVM разницы нет — производительность обоих типов одинакова. За что отвечают стандартные исключения JDK ▫️ checked говорят об ошибках с "внешними" причинами: файл не найден, поток прервали, сокет закрыт, указанный класс не найден. Исключения показывают возможные проблемы, которые в будущем могут повториться ▫️ unchecked указывают на ошибки в коде: передали null вместо объекта, пришёл некорректный аргумент, нельзя привести объект к указанному типу. Исправляются при обнаружении, и в будущем такая ошибка не ожидается Ошибки бизнес-логики Не найден пользователь, не хватает прав, превышен лимит снятия денег со счёта. Какие это исключения: checked или unchecked? В старых статьях по java и на многих курсах ответ однозначен. Исключения должны быть checked, чтобы ошибка не дошла до пользователя. На JavaRush и в других статьях пишут, что checked исключения никто не использует, потому что это неудобно. Кто же прав? Исключения бизнес-логики — ожидаемые события, которые нужно обработать. Пользователь должен увидеть не стектрейс, а красивое сообщение💅 Многие фреймворки облегчают работу с исключениями. Если в Spring задать обработчик для UserNotFoundException, то туда попадут UserNotFoundException из любой части сервиса. Spring в любом случае их поймает, поэтому исключения бизнес-логики делают unchecked. Код получается гораздо чище. По этой же причине checked иногда переводят в unchecked:
catch (SQLException e)
{ throw new IllegalStateException(e); }
Резюме
▫️ Если приложение написано на чистой java, то исключения бизнес-логики будут скорее всего checked
▫️ Если приложение использует фреймворк, который перехватывает исключения, их можно сделать unchecked
Правильные ответы на вопросы перед постом:
⭐️Вопрос 1: обработка checked исключений обязательна и проверяется на этапе компиляции
⭐️Вопрос 2: на практике чаще встречается
extends RuntimeExceptionно вариант
extends Exception тоже ок🔸 replace("A", "B")
Проверяет, что в строке вообще есть буквы А. Если да, создаётся новый массив символов и копируются значения исходной строки с заменой всех A на B
🔸 replaceAll("A", "B")
Раскрывается в обработчик регулярных выражений:
Pattern.compile("A").matcher(this).replaceAll("B")
🔸 replaceFirst("A", "B")
Также работает через RegEx, но заменяет только первое вхождение.
(в java 8 внутри replace тоже используются регулярки, и разница между replace и replaceAll очень туманна)
Что отсюда следует:
1️⃣ Переходите на java 11
2️⃣ Если вам не нужен функционал регулярок, используйте replace. Методы с Pattern.compile работают очень долго!
3️⃣ Давайте методам осмысленные имена. Должно быть понятно, что метод делает, и чем отличается от других. Автор методов replace* об этом не подумал, и теперь сотни проектов выбирают неподходящий вариант
❓ Есть ли разница между replace("a", "b") и replace('a', 'b')?
У replace два перегруженных варианта — для одиночных символов и для строк. В java 8 быстрее работает метод для символов, в java 11 между ними почти нет разницы
❓ Как удалить символы из строки?
В стандартной библиотеке нет метода remove. В java 11 для этой задачи подойдёт replace. В java 8 для критичных мест лучше взять StringUtils.remove из библиотеки Apache Commons Lang
Ниже — бенчмарки для разных джав и время в наносекундах. Результаты на разных железках могут отличаться!http://{{host}}:8080/book/12
Очень удобно определить переменные хоста и данные авторизации. Тогда их можно не прописывать в каждом запросе и быстро менять.
Переменные задаются на вкладке Environments:
▫️ Глобальные переменные доступны всем
▫️ Environment — наборы переменных, которые переключаются в выпадающем списке справа от запроса. Удобно сделать наборы для локальных вызовов, удалённого и тестового стенда.
Чтобы использовать переменную, запишите её в двойные скобочки {{host}}
4️⃣ Добавлять рандом в запросы и входные параметры
Начните набирать {{ и найдите в списке переменные random*
Их гигантское количество — рандомные числа, даты, страны, цвета, емейлы, банковские счета и даже биткойн-кошельки
5️⃣ Отправлять WebSocket и gRPS запросы
Пока в стадии бета, но я этим фичам очень рада🥰
Как найти: в левом верхнем углу под иконкой Postman находится имя вокспейса. Рядом с ним две кнопки — New и Import. Жмёте на New, выбираете нужный протокол
6️⃣ Добавить набор хэдеров во все запросы коллекции
Это делается с помощью скриптов.
Щёлкаете на коллекцию, переходите на вкладку pre-request Script, там пишете JS скрипт. Это несложно, там есть автозаполнение🙂 Для хедеров код выглядит так:
pm.request.addHeader({key:'Header1',value:'value2'});
Если вам нужна особенная генерация входных параметров, это тоже легко сделать через скрипты.
Больше подробностей — в документации
PS Убрала кнопку с сердечком, ставьте реакции, пожалуйста:) Так я буду понимать, какие темы нравятся, а какие не оченьКонструктор→поля→сеттерыВ остальном механизмы спринга работают одинаково: ▫️ Циклические зависимости возможны везде ▫️ Прокси создаются корректно ▫️ Отсутствующие зависимости обнаруживаются при компиляции или на старте приложения ❓ Откуда взялась эта рекомендация? Большинство авторов ссылаются на документацию. В разделе Dependency Injection указаны только два способа — конструктор и сеттер. Они универсальны и для XML конфигурации, и для аннотаций. В этом же разделе стоит рекомендация — использовать конструктор для обязательных и final полей, сеттер — для необязательных. Autowired над полями относится только к annotation-based конфигурации, поэтому находится в другом разделе. В документации нет ни единого упоминания, что Autowired над полями — это грех и хуже конструкторов и сеттеров. Рассмотрим основные аргументы против Autowired над полем: 😱 Слишком просто добавлять новые зависимости. Класс легко может потерять единственную ответственность. А когда в конструкторе 10 параметров, это сразу заметно 💁 Единственная ответственность Autowired — внедрить зависимость. Следить за дизайном — задача программиста 😱 Зависимость от DI-контейнера. Класс с аннотациями нельзя использовать за пределами проекта 💁 Ни разу не было такой потребности 😱 Полям нельзя добавить final 💁 Жаль, конечно, но мало кто в рантайме заменяет сервисы и репозитории 😱 Autowired поля проставляются через Reflection 💁 Если вам важно, что делает фреймворк под капотом, посмотрите на класс Constructor Resolver, там рефлекшена в 10 раз больше 😱 Непонятно, какие зависимости обязательные, а какие нет 💁 По умолчанию все Autowired зависимости обязательные. Если что-то необязательно, то можно поставить флажок (required=false) 😱 Непонятно, как тестировать такие классы без поднятия контекста 💁 10 лет назад было действительно никак. Сейчас юнит-тесты легко писать с помощью Mockito аннотаций @InjectMocks и @MockBean Итого Autowired над полями — самый лаконичный способ внедрить зависимость. Я не нашла ни одного весомого аргумента против. Для типичного энтерпрайз приложения этот способ идеально подходит. Лучшие практики и рекомендации формируются в своём контексте. Когда контекст меняется, практика может стать неактуальной. И это норм🙂
GET, PUT and DELETE идемпотентные, а POST — нет.
Но жизнь чуть сложнее.
🔹 Во-первых, идемпотентность зависит от бизнес-логики, а не от выбранного метода
Здесь самое сложное — держать под контролем сайд эффекты. Возьмём как пример увеличение счётчика в БД:
UPDATE t SET value=value+1Как сделать его идемпотентным? Добавить в таблицу (и сущность) поле version. Клиент передаёт номер текущей версии при обновлении. Запрос получается такой:
UPDATE t SET value=value+1, version=version+1 WHERE version=88🔹 Во-вторых, POST запросы можно сделать идемпотентными. Например так: Клиент генерирует ID и добавляет его в хэдер HTTP запроса:
Idempotency-Key: 4872934Сервис хранит у себя список ID недавних запросов. Если операции с таким ID ещё не было, сервис начнет выполнение. Процесс фильтрации дубликатов называется дедупликацией. ❓ Выглядит как лишняя сложность, нужна ли вообще идемпотентность? Исходная проблема — что делать с только что отправленными запросами при потере связи. Идемпотентность и повторная отправка — рабочий способ, но не единственный. О другом расскажу в следующем посте.
Available now! Telegram Research 2025 — the year's key insights 
