Используете ли вы Java 21 или просто обновили JDK?

Введение

Обновление JDK – это ещё не использование Java 21.

Многие команды переходят на новую версию платформы, чтобы «быть на LTS», но при этом продолжают писать код так, как будто всё ещё работают на Java 8 или 11. Между тем Java 21 приносит фундаментальные нововведения: виртуальные потоки, структурированную конкурентность, безопасные неизменяемые коллекции, мощные паттерны и прямой доступ к нативной памяти.

В этой статье мы затронем 15 ключевых практик, которые действительно раскрывают потенциал Java 21, особенно в сфере финансовых технологий. Каждая из них сопровождается:

  • Подтверждением доступности в LTS-релизе
  • Примером из реального финтех-сценария
  • Комментариями по производительности и безопасности

Если вы работаете над бэкендом корпортативной системы – этот материал поможет вам начать по-настоящему использовать Java 21, а не просто «ставить его в CI».

1. Используйте “record” для создания DTO

Что это: Синтаксический сахар для неизменяемых объектов.
Преимущества: Автоматически создаёт constructor, getters, equals, hashCode, toString. Удобен для DTO и value-объектов.
JEP: 395
Доступность: Финальная (Java 16+)
Пример кода:

public record Account(String id, String owner, double balance) {}

Account acc = new Account("ACC-42", "Elena", 3000.50);
System.out.println(acc.owner());

Производительность: меньше мусора в куче, меньше GC pressure.
Безопасность: поля final, нет сеттеров – снижает риск ошибок и race conditions.

2. Используйте “HttpClient” для REST-запросов

Что это: Современный HTTP-клиент, пришедший на смену HttpURLConnection.
Преимущества: Поддержка асинхронных запросов, простая конфигурация, стандартная библиотека.
JEP:
Доступность: Финальная (Java 11+)
Пример кода:

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://api.finapp.com/balance"))
    .header("Authorization", "Bearer token")
    .GET().build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println);

Производительность: асинхронные запросы не блокируют потоки.
Безопасность: строгая типизация, встроенная TLS-поддержка.

3. Используйте Structured Concurrency для координации задач

Что это: Новый способ запуска и контроля нескольких задач в рамках области (scope).
Преимущества: Управление зависимостями между задачами, автоматическая отмена и обработка ошибок.
JEP: 453
Доступность: Превью
Пример кода:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<Double> balance = scope.fork(() -> fetchBalance("ACC-1"));
    Future<Boolean> limitOk = scope.fork(() -> checkLimit("ACC-1", 500));

    scope.join();
    if (limitOk.resultNow()) {
        System.out.println("Доступно к переводу: " + balance.resultNow());
    }
}

Производительность: освобождение ресурсов при сбое одной из задач.
Безопасность: автоматическое завершение вложенных потоков, избегание утечек.

4. Используйте “Optional.orElseThrow()” для строгой валидации

Что это: Упрощённая проверка на null.
Преимущества: Лаконичный синтаксис, гарантия явной ошибки при отсутствии значения.
Доступность: Финальная (Java 10+)
Пример кода:

Optional<String> clientId = Optional.ofNullable(null);
String value = clientId.orElseThrow(() -> new IllegalStateException("Клиент не найден"));

Производительность: никаких лишних проверок, лучше JIT-инлайн.
Безопасность: отказ от null снижает вероятность NullPointerException.

5. Используйте Virtual Threads для масштабируемой обработки

Что это: Лёгкие потоки, управляемые JVM, а не ОС.
Преимущества: Поддержка миллионов конкурентных задач без издержек настоящих потоков.
JEP: 444
Доступность: Финальная
Пример кода:

var ids = List.of("TX-001", "TX-002", "TX-003");

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (String id : ids) {
        executor.submit(() -> {
            System.out.println("Обработка транзакции: " + id + " - " + Thread.currentThread());
        });
    }
}

Производительность: масштабируется в сотни тысяч задач.
Безопасность: совместимы с try-with-resources и Structured Concurrency.

6. Используйте Pattern Matching в “switch”

Что это: Расширенный switch, поддерживающий сопоставление с типами.
Преимущества: Безопасная обработка разных типов, отсутствие ручного instanceof.
JEP: 441
Доступность: Финальная
Пример кода:

void handleEvent(Object event) {
    switch (event) {
        case String s -> System.out.println("Комментарий: " + s);
        case Integer i -> System.out.println("Код события: " + i);
        case null -> System.out.println("Пустое событие");
        default -> System.out.println("Неизвестный тип");
    }
}

Производительность: лучше JIT-оптимизация по сравнению с instanceof.
Безопасность: исключает ошибки при приведении типа.

7. Используйте “List.of()” и “Map.of()” для безопасных коллекций

Что это: Фабрики для создания неизменяемых коллекций.
Преимущества: Безопасность в многопоточности, меньше кода.
Доступность: Финальная (Java 9+)
Пример кода:

Map<String, String> currencyMap = Map.of("USD", "Доллар", "EUR", "Евро");
List<String> topClients = List.of("Alice", "Bob", "Charlie");

Производительность: более эффективная реализация, чем ArrayList.
Безопасность: неизменяемость = отсутствие side-эффектов.

8. Используйте “String.repeat()” вместо циклов

Что это: Метод repeat() повторяет строку n раз.
Преимущества: Упрощение кода, форматирование шаблонов.
Доступность: Финальная (Java 11+)
Пример кода:

String maskedCard = "*".repeat(12) + "1234";
System.out.println(maskedCard); // 1234

Производительность: внутренне реализовано через буферы.
Безопасность: не требует циклов и ручных операций со строками.

9. Используйте Sequenced Collections для упорядоченных данных

Что это: Интерфейсы с гарантией порядка (getFirst(), getLast()).
Преимущества: Упрощённая работа с очередями, историями, логами.
JEP: 431
Доступность: Финальная
Пример кода:

SequencedSet<String> auditTrail = new LinkedHashSet<>();
auditTrail.add("START");
auditTrail.add("AML_CHECK");
auditTrail.add("APPROVED");

System.out.println(auditTrail.getFirst()); // START
System.out.println(auditTrail.getLast());  // APPROVED

Производительность: встроено в LinkedHashSet, без аллокаций.
Безопасность: стабильный порядок – предсказуемое поведение в логике.

10. Используйте “ScopedValue” вместо “ThreadLocal”

Что это: Альтернатива ThreadLocal для передачи контекста.
Преимущества: Безопаснее, быстрее, совместимо с виртуальными потоками.
JEP: 446
Доступность: Превью
Пример кода:

static final ScopedValue<String> SESSION_ID = ScopedValue.newInstance();

ScopedValue.where(SESSION_ID, "sess-abc123").run(() -> {
    System.out.println("Обработка сессии: " + SESSION_ID.get());
});

Производительность: дешевле ThreadLocal, без утечек.
Безопасность: значения живут в рамках области (run()), не сохраняются глобально.

11. Используйте “Stream.toList()” вместо “Collectors.toList()”

Что это: Новый способ преобразовать Stream в неизменяемый List.
Преимущества: Быстрее, безопаснее, лаконичнее.
Доступность: Финальная (Java 16+)
Пример кода:

List<String> clients = Stream.of("Alice", "Bob", "Charlie").toList();
System.out.println(clients);
// clients.add("Dave"); // UnsupportedOperationException

Производительность: лучше, чем Collectors.toList().
Безопасность: невозможно изменить – исключает side-effects.

12. Используйте Record Patterns для деструктуризации

Что это: Распаковка record в switch или if.
Преимущества: Читабельный, компактный код без ручного get().
JEP: 440
Доступность: Финальная
Пример кода:

record Payment(String from, String to, double amount) {}

void process(Payment p) {
    switch (p) {
        case Payment(String f, String t, double a) ->
            System.out.println("Перевод от " + f + " к " + t + ": " + a);
    }
}

Производительность: быстрое сопоставление, нулевая аллокация.
Безопасность: исключает ошибки доступа к полям.

13. Используйте Foreign Function & Memory API вместо JNI

Что это: Безопасный доступ к нативной памяти и функциям без JNI.
Преимущества: Простота, производительность, меньше ошибок.
JEP: 442
Доступность: Превью
Пример кода:

try (Arena arena = Arena.openConfined()) {
    MemorySegment seg = arena.allocate(100);
    String data = "FIN#12345";
    seg.asByteBuffer().put(data.getBytes());

    byte[] buffer = new byte[data.length()];
    seg.asByteBuffer().get(buffer);
    System.out.println(new String(buffer));
}

Производительность: быстрее JNI, меньше накладных расходов.
Безопасность: область памяти управляется JVM.

14. Используйте Smart “instanceof”

Что это: Автоматическое приведение типов при instanceof.
Преимущества: Уменьшает дублирование кода, повышает читаемость.
JEP: 394
Доступность: Финальная (Java 16+)
Пример кода:

Object value = "TXN-5678";
if (value instanceof String txnId) {
    System.out.println("Обрабатываем: " + txnId);
}

Производительность: лучше JIT-оптимизация.
Безопасность: исключает ClassCastException.

15. Используйте “String.stripIndent()” для шаблонов писем

Что это: Удаляет одинаковые отступы в многострочных строках.
Преимущества: Удобно для email- и лог-шаблонов.
Доступность: Финальная (Java 15+)
Пример кода:

String email = ```
        Hello,
            Your transaction has been processed.
        Regards,
        FinApp
        ```.stripIndent();

System.out.println(email);

Производительность: обрабатывается один раз при компиляции.
Безопасность: предотвращает формат-уязвимости в шаблонах.

Сводная таблица доступности

ФункциональностьJEPДоступность
1record395Финальная
2HttpClientФинальная
3Structured Concurrency453Превью
4Optional.orElseThrow()Финальная
5Virtual Threads444Финальная
6Pattern Matching in switch441Финальная
7List.of() / Map.of()Финальная
8String.repeat()Финальная
9Sequenced Collections431Финальная
10ScopedValue446Превью
11Stream.toList()Финальная
12Record Patterns440Финальная
13Foreign Function & Memory API442Превью
14Smart instanceof394Финальная
15String.stripIndent()Финальная