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)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
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 ещё не было, сервис начнет выполнение. Процесс фильтрации дубликатов называется дедупликацией. ❓ Выглядит как лишняя сложность, нужна ли вообще идемпотентность? Исходная проблема — что делать с только что отправленными запросами при потере связи. Идемпотентность и повторная отправка — рабочий способ, но не единственный. О другом расскажу в следующем посте.
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
