💡 Что такое рехеширование?
HashMap внутри хранит элементы в массиве бакетов. Когда количество элементов достигает порога capacity * loadFactor (по умолчанию 16 * 0.75 = 12), происходит resize: внутренний массив удваивается, а все элементы пересчитываются и перекладываются. Это O(n) операция, которая вызывает паузы. Фактор загрузки — компромисс между памятью и производительностью: ниже значение → меньше коллизий, но больше памяти; выше → экономия памяти, но чаще resize.
✅ Как правильно?
• Если знаешь будущее количество элементов, задавай new HashMap(expectedSize / 0.75f + 1), чтобы избежать resize.
• Для большинства случаев дефолтные 16 и 0.75 подходят.
• Если критична скорость и не жалко памяти, можно снизить loadFactor до 0.5.
⚠️ Нюанс: Частые ресайзы особенно болезненны при добавлении миллионов элементов. Лучше один раз выделить массив с запасом, чем много раз копировать данные.
Stream.iterate — мощный инструмент, но у него есть тёмная сторона. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не забывать про limit и takeWhile при работе с бесконечными стримами.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает коварство iterate.
Джун вешает @Scheduled(fixedRate = 3600_000) и ждёт, что задача запустится через час. Но Spring запускает её сразу после старта контекста. Приложение ещё не прогрето: пулы потоков не заполнены, соединения с БД не установлены, кэши пустые. Задача либо падает, либо забивает ресурсы в самый неподходящий момент. Сеньор добавляет initialDelay. Разбираем за 16 секунд.
💡 Зачем нужен initialDelay?
@Scheduled без initialDelay означает: первый запуск произойдёт немедленно после инициализации планировщика Spring, то есть в первые секунды старта приложения. Это опасно по нескольким причинам:
• Прогрев ещё не завершён. Пул соединений с БД может быть пуст, Hibernate ещё не поднял кэши, а @PostConstruct других бинов может не выполниться.
• Конкуренция за ресурсы. Старт приложения и так ресурсоёмкий: миграции, загрузка конфигов, warm-up. Дополнительная тяжёлая задача усугубляет.
• Каскадные ошибки. Если задача падает на старте, Spring может откатить инициализацию контекста или залогировать ошибку, которая маскирует реальные проблемы.
initialDelay задаёт паузу перед первым запуском. Это даёт приложению время прогреться: установить соединения, заполнить кэши, стабилизироваться. Периодичность (fixedRate или fixedDelay) начинает работать только после первой задержки.
⚠️ Нюанс: initialDelay не влияет на fixedRate и fixedDelay — они начинают отсчёт только после первого запуска. Если initialDelay больше периода, первый запуск всё равно произойдёт через initialDelay, а не через период. Также в Spring Boot можно задать глобальную задержку через spring.task.scheduling.initial-delay, но лучше указывать явно на каждом методе — разные задачи требуют разного времени прогрева.
OutOfMemoryError — это Error, а не Exception. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы понимать разницу между Error и Exception — это важно для написания надёжного кода.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает про Throwable.
💡 Что такое ExecutorService?
ExecutorService — это интерфейс из java.util.concurrent, который представляет пул потоков. Вместо того чтобы создавать Thread вручную, ты отправляешь задачи в пул, а он распределяет их между потоками. Потоки переиспользуются, что экономит ресурсы. Пул можно создать через фабрики Executors. По умолчанию задачи ставятся в очередь, если все потоки заняты.
✅ Когда использовать?
• Когда есть много независимых задач (обработка запросов, вычисления).
• Когда нужно ограничить количество одновременно работающих потоков.
• Когда нужен результат выполнения задачи (Future).
⚠️ Нюанс: ExecutorService не завершается автоматически. Если не вызвать shutdown() или shutdownNow(), приложение может не остановиться. shutdown() перестаёт принимать новые задачи и ждёт завершения текущих, shutdownNow() пытается прервать выполняющиеся задачи. Также не забывай про awaitTermination, если нужно дождаться полного завершения пула.
Лямбды реализуют функциональные интерфейсы, и не все исключения можно просто так пробросить. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не ловить ошибки компиляции при работе со стримами и checked-исключениями.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает ограничения лямбд.
Джун тащит HttpServletRequest в метод и парсит заголовок через getHeader. Сеньор ставит @RequestHeader на параметр — Spring делает всё сам. Разбираем за 16 секунд.
💡 Почему @RequestHeader лучше?
@RequestHeader — это аннотация Spring MVC, которая связывает значение HTTP-заголовка с параметром метода контроллера. Spring сам достаёт заголовок, конвертирует в нужный тип (String, int, enum, UUID, дата и т.д.) и, если заголовка нет, возвращает 400 Bad Request — без ручных проверок на null.
Преимущества:
• Меньше кода: одна аннотация вместо ручного парсинга и null-проверок.
• Типобезопасность: Spring сконвертирует значение в нужный тип.
• Слабая связанность: метод контроллера не зависит от HttpServletRequest, его проще тестировать.
• Валидация: можно совместить с @Validated, @NotNull, @Pattern.
⚠️ Нюанс: Для нескольких заголовков одного имени используйте @RequestHeader MultiValueMap headers или HttpHeaders. А для заголовков, которые часто используются (например, Authorization), Spring Security даёт готовые механизмы — не парсите его руками.
try без catch, только finally. Внутри бросается исключение. Многие думают, что finally его «съест». Но Java так не работает. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не терять исключения из-за finally — это классическая ловушка.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает порядок выполнения.
💡 Что такое CopyOnWriteArrayList?
CopyOnWriteArrayList — потокобезопасная реализация List из пакета java.util.concurrent. Его главная особенность: при каждой модификации (add, set, remove) создаётся новая копия внутреннего массива. Старый массив остаётся неизменным, и все текущие итераторы продолжают работать с ним. Это даёт fail-safe поведение: изменение списка во время итерации не вызывает ConcurrentModificationException.
Чтение (get, итерация) работает без блокировок и очень быстро — массив не меняется, пока не произойдёт запись. Запись, наоборот, дорогая: копирование всего массива занимает O(n) и создаёт нагрузку на память. Итератор работает со snapshot и не видит элементов, добавленных после его создания — чтобы получить актуальные данные, нужно получить новый итератор.
✅ Когда использовать?
• Много чтений, мало записей.
• Список слушателей событий, подписчиков, обработчиков.
• Кэш конфигураций, который обновляется редко.
• Ситуации, когда итерация не должна прерываться при модификации.
⚠️ Нюанс: Не используй CopyOnWriteArrayList при частых записях — копирование массива на каждый add убьёт производительность. Для частых изменений подойдут ConcurrentLinkedQueue, ConcurrentHashMap.newKeySet() или Collections.synchronizedList(new ArrayList()). Также помни, что fail-safe итератор — это не «магическая защита»: если логика зависит от актуальных данных, snapshot может ввести в заблуждение.
Pattern matching для instanceof появился в Java 16. Как он ведёт себя с null? Многие ждут NPE, но Java удивляет. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не бояться instanceof с null — этот паттерн безопасен.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает про pattern matching.
Джун загружает 100 пользователей и их заказы. Hibernate делает 101 запрос. Сеньор ставит @BatchSize(size = 50) и получает 3 запроса.
💡 Что такое N+1 и как @BatchSize его лечит?
Когда ты загружаешь список сущностей, а у каждой есть ленивая коллекция, Hibernate по умолчанию идёт в базу за каждой коллекцией отдельно. Это N+1: один запрос на список + N запросов на каждую коллекцию.
@BatchSize(size = N) говорит Hibernate: «Когда один из пользователей запросит свои заказы, подгрузи заказы сразу для N пользователей одним запросом». То есть вместо 100 отдельных SELECT-ов уйдёт 2 пачки по 50. Плюс один изначальный — итого 3.
Настройка действует на уровне коллекции (или на уровне класса для сущностей).
⚠️ Нюанс: @BatchSize не заменяет JOIN FETCH. Если данные нужны сразу в одном запросе — используй JOIN FETCH или @EntityGraph. @BatchSize — когда коллекции нужны лениво и хочется избежать N+1 без дублирования строк. Комбинация обоих подходов даёт лучший результат.
Параллельный стрим обычно не гарантирует порядок. Но что, если использовать forEachOrdered? В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не путать forEach и forEachOrdered — это важно при параллельной обработке.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает тонкости Stream API.
💡 В чём разница?
throw — это оператор, который создаёт и выбрасывает объект исключения. Например, throw new IOException("...").
throws — это ключевое слово в сигнатуре метода, которое объявляет, что метод может выбросить одно или несколько checked-исключений. Например, void readFile() throws IOException. Оно информирует вызывающий код о необходимости обработки (try-catch или проброс выше). Для unchecked-исключений (наследников RuntimeException) throws не обязателен, потому что компилятор не требует их обработки.
✅ Как правильно?
• Бросаешь исключение в теле метода? Используй throw.
• Метод может выбросить checked-исключение? Объяви через throws.
• Для unchecked throws опционален, но может быть добавлен для документирования.
⚠️ Нюанс: Один метод может объявлять несколько исключений: throws IOException, SQLException. Если метод переопределяет метод родителя, он не может объявлять более широкие checked-исключения, чем родительский.
Константный hashCode не сломает HashMap сразу, но заставит её работать как LinkedList. Сколько элементов сохранится и почему это плохо? В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не убить производительность своих мап — это классическая ловушка.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает про коллизии.
Джун блокирует поток через Thread.sleep для каждой отложенной задачи. Сеньор использует DelayQueue — задачи ждут своего времени, не мешая другим. Разбираем за 16 секунд.
💡 Почему sleep — плохо для отложенных задач?
Thread.sleep(ms) блокирует весь поток на указанное время. Если нужно выполнить 10 задач с задержкой в 5 секунд, получишь 50 секунд полного простоя. Поток не может делать ничего другого. Кроме того, sleep не даёт точного порядка: задачи выполняются строго по очереди, даже если их время пришло одновременно.
DelayQueue — это очередь, где каждый элемент реализует интерфейс Delayed с методом getDelay(). Поток вызывает take() и блокируется только до момента, когда первый элемент станет готов. Если элементы ещё не готовы, поток ждёт, но как только первый «созрел» — получает его. Одновременно готовые задачи извлекаются в порядке getDelay(). Это эффективно и не блокирует поток зря.
⚠️ Нюанс: DelayQueue не гарантирует порядок для элементов с одинаковым временем — только по getDelay(). Если нужна строгая последовательность, добавьте счётчик или используйте PriorityQueue с Delayed.
Массивы в Java ведут себя не так, как дженерики. Одно безобидное присваивание может уронить программу в рантайме. В видео — короткий пример и четыре варианта ответа. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не попасться на ArrayStoreException — это частая проблема при рефакторинге и наследовании.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает про ковариантность.
💡 Что такое Serial GC?
Serial GC — это самый базовый сборщик мусора в HotSpot JVM. Он выполняет сборку мусора в одном потоке, что означает полную остановку всех потоков приложения (stop-the-world) на время очистки. Отсутствие многопоточности уменьшает накладные расходы на синхронизацию и делает его эффективным для небольших куч (примерно до 100-200 МБ). Включается флагом -XX:+UseSerialGC.
✅ Когда использовать?
• Маленькие клиентские приложения, где куча невелика.
• Встраиваемые системы, микроконтроллеры (если Java там вообще используется).
• Контейнеры с жёсткими ограничениями памяти и CPU.
• Тестирование или простые скрипты, где паузы не важны.
⚠️ Нюанс: Для серверных приложений с большой кучей и многопоточностью Serial GC не подходит: паузы будут слишком длинными. В таких случаях выбирают Parallel GC, G1, ZGC или Shenandoah, которые распределяют работу между потоками или делают паузы минимальными.
PriorityQueue — отличный выбор для очереди с приоритетом, но есть нюанс с null. Один вызов add может уронить программу. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не наступить на NPE при работе с очередями — это случается чаще, чем хотелось бы.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает ограничения коллекций.
Джун создаёт новый BigInteger для единицы каждый раз, когда она нужна. Сеньор использует константу BigInteger.ONE. Разбираем за 16 секунд.
💡 Почему new BigInteger("1") — плохо?
BigInteger — неизменяемый класс. Это значит, что объект BigInteger.ONE можно безопасно переиспользовать в любом месте, где нужна единица. Создание нового объекта через new BigInteger("1") каждый раз приводит к лишним аллокациям, особенно в циклах или часто вызываемых методах. В больших системах это увеличивает нагрузку на сборщик мусора. Кроме того, BigInteger.ONE — это стандартная константа, которая делает код понятнее.
✅ Как правильно?
Используй готовые константы:
• BigInteger.ZERO
• BigInteger.ONE
• BigInteger.TEN
Они создаются при загрузке класса и доступны везде.
remove в ArrayList перегружен: один метод принимает int, другой Object. Когда в списке Integer, вызов remove(1) может удалить не то, что вы ожидали. В видео — короткий пример и четыре варианта. Выбери свой, а завтра я покажу правильный разбор.
📌 Сохрани пост, чтобы не путать удаление по индексу и по значению — это классическая ловушка с автоупаковкой.
💬 Напиши в комментариях, какой вариант выбрал — посмотрим, кто знает приоритет сигнатур.
