Слогер Создать блог
Разработка

Разбор: 5 возможностей Java 21, которые меняют ежедневный код

Java 21 вышла в сентябре 2023 года, но во многих проектах код внутри остался от Java 11. Вот пять возможностей, которые уже финализированы, — с подводными камнями и чек-листом внедрения.

Java 21 — версия с долгосрочной поддержкой, она вышла в сентябре 2023 года. Переход во многих проектах закончился сменой цифры в файле сборки: код внутри остался тем же, что был на Java 11. Цепочки instanceof с кастами, пулы потоков на двести задач, ручной обход LinkedHashSet ради последнего элемента. Всё компилируется и работает — просто читается хуже, чем могло бы, и такие вещи регулярно всплывают на технических собеседованиях.

Ниже пять возможностей, финализированных в Java 21, без флага preview. Их можно начинать применять хоть в понедельник.

1. Виртуальные потоки: масштаб вместо скорости

Обычный Thread — обёртка над потоком операционной системы. Создавать их дорого, и их не так много, поэтому годами все жили с ограниченными пулами и асинхронным кодом.

JEP 444 финализирует виртуальные потоки. Это по-прежнему экземпляры java.lang.Thread, но JVM планирует их поверх нескольких потоков ОС. Когда виртуальный поток блокируется на операции ввода-вывода из JDK, он размонтируется и освобождает носитель под другую задачу.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(task); }

Главное, что понимают не все: это не «быстрые потоки». Растёт пропускная способность там, где тысячи задач большую часть времени ждут — HTTP-запросы, обращения к базе. На нагрузке, упирающейся в процессор, выигрыша не будет вообще.

Подводный камень именно в Java 21: внутри блока synchronized виртуальный поток пиннится и держит носитель. В Java 24 это исправили через JEP 491, а на 21-й стоит пройтись по synchronized, которые оборачивают сетевые вызовы, и заменить их на ReentrantLock.

2. Паттерны в switch

Раньше выбор действия по типу объекта — это лестница if / else if с instanceof. JEP 441 финализирует паттерны в switch после четырёх раундов preview, начиная с Java 17.

static String formatter(Object obj) { return switch (obj) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case String s -> String.format("String %s", s); default -> obj.toString(); }; }

Селектором может быть любой ссылочный тип, каждый case принимает типовой паттерн, как в instanceof. Побеждает первый совпавший case в порядке написания.

Ключевой момент: сравнение идёт по фактическому типу объекта. Значение роли не играет — Object, в котором лежит Long, не подойдёт под case Integer i, даже если число помещается в int. Зато компилятор сообщит, если какой-то case недостижим, потому что его уже перекрыл предыдущий.

3. case null и охранные условия when

Switch с null-селектором раньше бросал NullPointerException, поэтому проверка жила снаружи, в if. Теперь её можно занести внутрь через case null, а к каждому case добавить условие через when:

switch (s) { case null -> System.out.println("Oops!"); case String v when v.equalsIgnoreCase("YES") -> ...; case String v -> ...; }

Guard срабатывает, только если паттерн совпал и условие истинно. Если нет — switch продолжает перебирать следующие case. Порядок важен: охраняемый case ставится раньше общего, иначе общий его перекроет, и компилятор сообщит об ошибке.

И ещё одно: guard бывает только у паттернов. case "hello" when ... не скомпилируется — константа паттерном не является.

4. Паттерны записей

Pattern matching в instanceof избавляет от каста, но вызовы p.x() и p.y() остаются. JEP 440 финализирует record patterns, которые достают компоненты прямо в паттерне.

record Point(int x, int y) {} ... if (obj instanceof Point(int x, int y)) { System.out.println(x + y); }

Паттерн проверяет тип и, если совпало, вызывает аксессоры записи. Имена переменных не обязаны совпадать с именами компонентов, в каждой позиции допустим var, работает всё одинаково и в instanceof, и в метках case. Паттерны вкладываются друг в друга: совпасть должно сразу всё, и если часть не совпала — исключения не будет, ветка просто не выполнится.

В связке со switch по sealed-иерархии каждая форма данных разбирается одной строкой, без кастов, а компилятор проверяет, что ни один случай не пропущен.

5. Sequenced-коллекции

Достать первый и последний элемент раньше зависело от типа: list.get(0) и list.get(list.size() - 1) у списка, deque.getFirst() у Deque, sortedSet.first() у SortedSet. А у LinkedHashSet последний элемент приходилось искать полным обходом.

JEP 431 добавляет SequencedCollection, SequencedSet и SequencedMap — общий контракт для коллекций с определённым порядком, встроенный в существующую иерархию.

String first = list.getFirst(); String last = list.getLast(); String lastInSet = linkedHashSet.getLast();

Кроме getFirst и getLast есть addFirst, addLast, removeFirst, removeLast и reversed(). С последним осторожно: он не копирует коллекцию, а отдаёт представление в обратном порядке. И на пустой коллекции getFirst() с getLast() бросают NoSuchElementException — ровно как это всегда делал Deque.

Шпаргалка

ВозможностьJEPЧто убирает из кодаГде не поможет
Виртуальные потоки444ограниченные пулы, асинхронные обёрткизадачи, упирающиеся в процессор
Паттерны в switch441цепочки instanceof и явные кастыкогда логика зависит не от типа
case null и when441проверку на null перед switchу константных case — guard туда не поставить
Паттерны записей440касты и вызовы аксессоровобъекты обычных классов, не record
Sequenced-коллекции431get(0), get(size()-1), полный обходколлекции без порядка, например HashSet

Что ломается на практике

  • Ждут ускорения от виртуальных потоков на CPU-bound нагрузке и получают те же цифры, что были.
  • Оставляют synchronized вокруг сетевых вызовов, а потом удивляются, почему носители всё равно упираются в потолок.
  • Ставят общий case раньше охраняемого и ловят ошибку компиляции.
  • Рассчитывают, что case Integer i поймает Long с подходящим значением. Не поймает: сравнивается тип.
  • Думают, что reversed() создаёт новую коллекцию, и меняют исходный список, ожидая независимости.

Проверочная задача на null

Есть метод, который разбирает объект через switch с default, но без case null:

static String describe(Object o) { return switch (o) { case String s -> "text"; case Integer i -> "number"; default -> "something else"; }; }

Вызов describe(null) не вернёт "something else" и не отдаст "number". Он бросит NullPointerException: default null не ловит, до него дело не доходит. Чтобы обработать null, нужен явный case null — или комбинированный case null, default, если уместно свалить его в общую ветку. Код при этом компилируется без единого предупреждения, поэтому такие места стоит искать глазами.

С чего начать

Порядок внедрения зависит от риска, а не от красоты синтаксиса. Sequenced-коллекции и паттерны записей почти не меняют поведение кода — это локальные правки, их можно раскатывать по файлу за файлом. Дальше switch с паттернами и case null: тут уже появляется риск поймать null в новом месте, зато исчезают лестницы из instanceof. Виртуальные потоки — в последнюю очередь, потому что перед ними нужно пройтись по synchronized вокруг всего, что ходит в сеть или в базу.

И отдельно про интервью: вопросы про пиннинг виртуальных потоков, про то, что case Integer i не ловит Long, и про то, копирует ли reversed() коллекцию, встречаются регулярно. Ответы на них — ровно те пять пунктов выше.

По материалам: career. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.

Карьера

Как ИИ-пайплайн превращает 1500 вакансий в 12 откликов: разбор пяти стадий поиска работы

Автор прогнал через систему больше 1500 объявлений и оставил в очереди двенадцать. Разбираем, как устроены стадии, где правила работают лучше модели и почему последнюю милю нельзя автоматизировать.

Слогер 09.10.2026 ▲ 0
Fluxs lenta
Реклама · fluxs.ru
Разработка

Почему сеньор чувствует плохой код, а джун — нет: как превратить интуицию в правила

Разбор ситуации с dev.to: джун спросил наставника, откуда тот знал, что код неправильный, — и ответа не последовало. Что стоит за этим «чутьём» и как передать его другому человеку.

Слогер 08.10.2026 ▲ 0
Образование

Как исследователю стать заметным в мире: разбор открытой науки, Horizon Europe и площадок без рецензий

Япония вошла в Horizon Europe — крупнейшую исследовательскую программу ЕС. Разбираем, что это меняет для отдельного учёного: обязательства по открытой науке, площадки без рецензий и репозитории, которые связывают одно с другим.

Слогер 08.10.2026 ▲ 0
AI

Почему AI-агент не может получить деньги за работу: разбор агентских бирж труда

Инфраструктура для агентской работы уже построена — биржи, каталоги, расчёты в стейблкоинах. Ломается всё на последнем шаге: как исполнитель без кошелька и банковского счёта получает оплату.

Слогер 08.10.2026 ▲ 0
Fluxs lenta
Реклама · fluxs.ru
Образование

Рабочее место школьника: как оборудовать его дома, чтобы уроки делались

Свет, посадка, хранение и гаджеты — четыре вещи, которые решают, будет ли ребёнок заниматься за столом или начнёт искать повод встать. Ниже — порядок обустройства и список того, что можно не покупать.

Sloger 07.10.2026 ▲ 0
AI

Анализ тендерной документации: как читать ТЗ и проект контракта за минуты, а не за вечер

Пять файлов, ТЗ на несколько мегабайт и день до окончания подачи. Знакомая картина для всех, кто участвует в тендерах. Написали в блоге, как разбирать тендерную документацию быстро и не пропустить главное: — где искать сроки, обеспечение, оплату, штрафы и гарантию (подсказка: почти всё в проекте контракта, а не в ТЗ); — почему технологии в ИТ-закупках почти никогда не пишут в названии; — как ИИ-разбор в Fluxs выписывает условия с дословными цитатами и сверяет их с документом; — зачем отдельный список вопросов для запроса разъяснений. Решение об участии остаётся за человеком. ИИ просто экономит вечер над ТЗ. https://fluxs.ru/blog/analiz-tendernoj-dokumentacii

Слогер 07.10.2026 ▲ 1
Fluxs lenta
Реклама · fluxs.ru