null

Что нового в Java 21?

Введение

Java 21 - это свежий крупный релиз известного во всем мире и многими любимого языка программирования.

Релиз включает в себя ряд новых возможностей и улучшений:

- Строковые шаблоны (String Templates)

- Упорядоченные коллекции (Sequenced Collections)

- Сборщик мусора Generational Z (ZGC)

- Record Patterns (сопоставление Records с образцом)

- Сравнение с образцом для оператора switch (Pattern Matching for switch)

- Foreign Function & Memory API

- Безымянные образцы (шаблоны) и переменные (Unnamed Patterns and Variables)

- Виртуальные потоки (Virtual Threads)

- Безымянные классы и методы экземпляра Main (Unnamed Classes and Instance Main Methods)

- Значения с ограниченной областью действия (Scoped Values)

- Vector API

- Упразднение порта JDK для Windows 32-bit x86 с полным отказом от него (удалением) в последующих версиях языка

- Подготовка к запрету динамической загрузки агентов (Prepare to Disallow the Dynamic Loading of Agents)

- API механизма инкапсуляции ключей (Key Encapsulation Mechanism API)

- Структурированный параллелизм (Structured Concurrency)

В этой статье мы рассмотрим некоторые основные из них.

И так, начнем.

1. Виртуальные потоки (Virtual Threads)

В Java 21 введены виртуальные потоки для упрощения и масштабирования параллелизма. Это шаг вперед, чтобы сделать параллельное программирование более доступным и эффективным.

Виртуальные потоки, также известные как "потоки пользовательского режима" или "волокна", являются частью структурированного параллелизма в JDK 21. Они являются легковесными, и это означает, что их можно создавать в бОльшем количестве, чем традиционные потоки, и с гораздо меньшими накладными расходами. При грамотном подходе к их использованию, даже в программах с высокой пропускной способностью, может стать более практичным выполнение отдельных задач или запросов в собственных виртуальных потоках.

Существует несколько способов создания и использования виртуальных потоков в Java 21:

1) Используя статический builder-метод и builder (построитель). Виртуальный поток можно создать с помощью статического builder-метода, который принимает в качестве параметра runnable и сразу же запускают виртуальный поток. Также поток можно инициализировать с помощью построителя и в этом случае потоку можно задать некоторые свойства, например, имя.

Это показано в следующем примере:

Runnable runnable = () -> {
  System.out.println("Hello from tune-it!");
};

// Используем статический builder-метод.
Thread virtualThread = Thread.startVirtualThread(runnable);

// Используем builder
Thread.ofVirtual()
      .name("my-virtual-thread")
      .start(runnable);

2) Используя ExecutorService с виртуальными потоками. Начиная с 5 версии языка Java, разработчикам рекомендуется использовать ExecutorServices вместо вызова класса Thread напрямую.

В Java 21 появился новый ExecutorService, использующий виртуальные потоки.

Приведем пример:

Runnable runnable = () -> {
  System.out.println("Tune-it greets you!");
};

try (ExecutorService executorService = 
Executors.newVirtualThreadPerTaskExecutor()) {
  for (int i = 0; i < 100; i++) {
      executorService.submit(runnable);
  }
}

Этот код создает ExecutorService внутри оператора try-with-resource, создавая виртуальный поток для каждой утвержденной (submitted) задачи.

3) Используя фабрику виртуальных потоков. Вы можете создать фабрику, которая будет производить для вас виртуальные потоки.

Вот пример:

Runnable runnable = () -> {
  System.out.println("Hello, everyone!");
};

ThreadFactory virtualThreadFactory = Thread.ofVirtual()
      .name("prefix", 0)
      .factory();

Thread factoryThread = virtualThreadFactory.newThread(runnable);
factoryThread.start();

Этот код создает фабрику виртуальных потоков, и каждый виртуальный поток, инициализированный с помощью этой фабрики, будет иметь имя, содержащее префикс и заканчивающееся числом.

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

2. Структурированный параллелизм

Структурированный параллелизм, который в настоящее время находится в стадии тестирования, упрощает программирование параллельных задач. Он позволяет рассматривать группы связанных задач, выполняемых в разных потоках, как единое целое. Это упрощает обработку ошибок и отмены, повышает надежность кода и облегчает понимание происходящего. Эта концепция уже была опробована в JDK 19 и JDK 20, вышедших в марте и сентябре 2022 года. Она будет протестирована еще раз в качестве предварительной версии в пакете util.concurrent. Единственным существенным отличием на этот раз является то, что метод StructuredTaskScope::(...) теперь возвращает [Subtask] вместо Future. Целью структурированного параллелизма является поощрить такой способ параллельного программирования, который избавляет от распространенных проблем, возникающих при отмене или завершении работы, таких как утечка потоков и задержки при отмене, а также облегчить понимание параллельного кода.

3. Сборщик мусора Generational Z (ZGC)

Сборщик мусора Generational Z Garbage Collector (ZGC) предназначен для повышения производительности приложений за счет поддержания отдельных поколений для молодых и старых объектов. Молодые объекты, как правило, умирают молодыми, поэтому разделение поколений позволяет ZGC чаще собирать молодые объекты. Приложения, работающие с поколенческим ZGC, потенциально имеют меньший риск возникновения сбоев при распределении памяти, меньшие требуемые затраты памяти кучи и меньшие затраты процессора на сборку мусора. Эти преимущества потенциально достижимы без существенного снижения пропускной способности по сравнению с GС без поколений.

4. API механизма инкапсуляции ключей (Key Encapsulation Mechanism (KEM) API)

В области шифрования KEM API является существенным дополнением. Он позволяет приложениям использовать передовые алгоритмы KEM для защиты симметричных ключей с помощью открытой криптографии, поддерживая шифрование нового поколения.

5. Отказ от ветки (порта) JDK для Windows 32-bit x86

Порт для Windows 32-bit x86 помечен как кандидат на удаление в одном из будущих выпусков JDK. Это изменение связано с невозможностью использования виртуальных потоков в 32-битных средах, а также с окончанием срока поддержки 32-битной версии Windows 10 в октябре 2025 года.

6. Безымянные классы и методы экземпляра Main (Unnamed Classes and Instance Main Method)

В Java 21 представлены две новые возможности языка: безымянные классы и новый протокол запуска, позволяющий запускать классы Java более просто и без большого количества стандартизированного текста.

1) Безымянный Java-класс - это возможность запускать вашу программу в Java-файле, у которого нет имени класса. Таким образом, вам больше не придется помещать запускаемый метод внутри оператора class.

2) Улучшенный протокол запуска. Теперь вы можете запускать свою программу с помощью простого метода main, который не является статическим. Этот метод не требует каких-либо особых входных данных и является отправной точкой для вашего класса.

Давайте рассмотрим все это на примере знаменитого hello world:

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

И так, мы видим, что в исходном классическом варианте получается слишком много стандартизированного текста, слишком много кода.

Однако если применить улучшенный протокол запуска, код будет выглядеть гораздо проще:

class HelloWorld {
  void main() {
     System.out.println("Hello, World!");
  }
}

Однако, это еще не все. Давайте применим безымянные классы, чтобы еще сильнее упростить код:

void main() {
   System.out.println("Hello, World!");
}

7. Улучшенное сравнение с образцом. Безымянные образцы (шаблоны) и переменные (Unnamed Patterns and Variables). Сопоставление Records с образцом.

i) Сравнение с образцом предполагает проверку наличия у объекта определенной структуры и извлечение из него данных в случае совпадения.

В Java 21 оно было улучшено, - стало более лаконичным и читабельным.

Например, использование сравнения в операторе "if" позволяет напрямую использовать сопоставленный объект:

if (obj instanceof Circle c) {
  System.out println("Radius: " + c.getRadius());
}

ii) Еще одной новой возможностью языка Java 21 являются безымянные шаблоны и переменные.

Наряду с "безымянными классами и методами экземпляра main", они направлены на улучшение читаемости и сопровождаемости кода.

Например, если вы объявили в коде переменную, но не собираетесь ее использовать, то теперь ее можно заменить символом подчеркивания (_). Это может применяться в различных сценариях, например, в блоках try-catch, циклах for и при сопоставлении records с образцом (record pattern matching).

В этих двух примерах переменная 'e' и переменная 'c' не используются:

try { 
  ...
  } catch(Exception e) {
          System.out.println("An error has occurred!");
  }

for(Car c: carList) {
            ++count;   
  }

Поэтому для удобства мы можем заменить их безымянными переменными:

try {
    ...
  } catch(Exception _) {
          System.out.println("An error has occurred!");
  }

for(Car _: carList) {
            ++count; 
  } 

iii) Сравнение Records с образцом (Record Patterns).

Record Patterns могут использоваться для упрощения и улучшения читаемости кода, работающего с объектами типа Record.

Например, следующий код использует сравнение с безымянным образцом для выявления Record, у которой задано определенное значение поля x:

record Point(int x, int y) {}

Point point = Point(10, 20);
if (point == Point(10, _)) { // Сравниваем с экземпляром point у которого x = 10
  // выполняем действия с point
}

 

8. Scoped values (значения с ограниченной областью действия, ограниченные значения)

Также продолжает тестироваться новая фича (которая уже была опробована в JDK 20), позволяющая обмениваться данными, которые не могут быть изменены внутри потоков и между ними. Речь идет о Scoped values. Это нововведение считается более лучшей практикой, чем использование локальных переменных потоков, особенно при использовании большого количества виртуальных потоков. Локальные переменные потоков имеют ряд проблем, например, их можно слишком часто изменять, они могут храниться слишком долго, и их использование может быть дорогостоящим. Scoped value позволяет безопасно обмениваться данными между частями большой программы без использования аргументов методов.

9. Сравнение с образцом для оператора switch (Pattern Matching for switch)

Java 21 расширяет возможности switch-выражений, позволяя использовать более гибкие шаблоны и упрощая условную логику.

К примеру:

static void check(Object obj) {
    String formatted = switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}

Или :

String level = getLevel();

int levelCode = switch (level) { 
case "Easy" -> 1; 
case "Normal" -> 2;
case "Hard", "Skilled" -> 3; 
case "Impossible" -> 4;
default -> break;
};

10. Упорядоченные коллекции (Sequenced Collections)

Упорядоченные коллекции - это новый тип коллекций, который обеспечивает прямой доступ к первому и последнему элементам коллекции. Это может быть полезно для приложений, которым необходимо выполнять итерации по коллекции в определенном порядке. Упорядоченные коллекции реализуются с использованием структуры данных связанного списка (linked list). Это делает их эффективными для итераций по коллекции в прямом или обратном направлении.

interface SequencedCollection<E> extends Collection<E> {
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();

    // новый метод
    SequencedCollection<E> reversed();
}
import java.util.ArrayList;
import java.util.List;
import java.util.SequencedCollection;

public class SequencedCollectionExample {

  public static void main(String[] args) {
    List<String> strings = new ArrayList<>();
    strings.add("Hello");
    strings.add("from Tune-it!");

    // Получаем упорядоченную коллекцию с реверсированным содержимым исходного списка строк:
    SequencedCollection<String> reversedStrings = strings.reversed();
    for (String string : reversedStrings) {
      System.out.println(string);
    } // напечатает "from Tune-it!" "Hello"
  }
}

11. Строковые шаблоны (String Templates)

Строковые шаблоны - это новая возможность в Java 21, которая позволяет интерполировать выражения в строковые литералы. Это может быть полезно для генерации динамического текста, например, сообщений об ошибках или персонализированных приветствий.

Синтаксис строковых шаблонов выглядит следующим образом:

String name="Tune-it";
String text=STR. "The company name is {name}.";

STR - это обработчик шаблонов, который сообщает компилятору, что это строковый шаблон. Точка (.) является разделителем между обработчиком шаблонов и строкой шаблона. Строка шаблона заключена в двойные кавычки. Выражения, подлежащие интерполяции, заключены в фигурные скобки.

В данном примере выражение {name} будет интерполировано со значением переменной name и в итоге получится строка "The company name is Tune-it".

Платформа Java предоставляет два обработчика шаблонов:

STR: Это обработчик шаблонов по умолчанию. Он выполняет интерполяцию строк.

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

Заключительные мысли

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

Независимо от того, являетесь ли вы опытным разработчиком или начинающим, все эти улучшения призваны сделать ваш опыт программирования на Java более приятным и эффективным.

---------------------------------------------------------------------

При подготовке статьи использовались следующие материалы:

"Java Latest Version Update — Java 21" автора Abdul Kadhar

"Java 21: Unleashing Exciting Updates for All Developers!" автора Harshit Raj

"The New Features of Java 21" автора Kesk -*-