Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
Больше📈 Аналитический обзор Telegram-канала Java: fill the gaps
Канал Java: fill the gaps (@java_fillthegaps) языкового сегмента Русский является активным участником. Сейчас сообщество объединяет 12 552 подписчиков, занимая 10 101 место в категории Технологии и приложения и 52 755 место в регионе Россия.
📊 Показатели аудитории и динамика
С момента создания невідомо проект демонстрирует стремительный рост, собрав аудиторию из 12 552 подписчиков.
Согласно последним данным от 05 июня, 2026, канал показывает стабильную активность. За последние 30 дней изменение числа участников составило -49, а за последние 24 часа — -4, при этом общий охват остаётся высоким.
- Статус верификации: Не верифицирован
- Уровень вовлечённости (ER): Средний показатель вовлечённости аудитории составляет 34.71%. В первые 24 часа после публикации контент обычно набирает N/A% реакций от общего числа подписчиков.
- Охват публикаций: В среднем каждый пост получает 0 просмотров. В течение первых суток публикация набирает 0 просмотров.
- Реакции и взаимодействия: Аудитория активно поддерживает контент: среднее количество реакций на один пост — 0.
- Тематические интересы: Контент сосредоточен на ключевых темах, таких как redis, hashmap, linkedhashmap, индекс, фича.
📝 Описание и контентная политика
Автор описывает ресурс как площадку для выражения субъективного мнения:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
Благодаря высокой частоте обновлений (последние данные получены 07 июня, 2026) канал поддерживает актуальность и высокий уровень охвата публикаций. Аналитика показывает, что аудитория активно взаимодействует с контентом, что делает его важной точкой влияния в категории Технологии и приложения.
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 ещё не было, сервис начнет выполнение. Процесс фильтрации дубликатов называется дедупликацией. ❓ Выглядит как лишняя сложность, нужна ли вообще идемпотентность? Исходная проблема — что делать с только что отправленными запросами при потере связи. Идемпотентность и повторная отправка — рабочий способ, но не единственный. О другом расскажу в следующем посте.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
