Java 21: нові API, відмова від 32-бітної x86-версії для Windows та покращення Z Garbage Collector

💡 Усі статті, обговорення, новини про Java — в одному місці. Приєднуйтесь до Java спільноти!

Привіт, мене звати Андрій. Програмуванням я зацікавився ще у восьмому класі, тож вступив до технічного вишу. Старт моєї карʼєри в ІТ прийшовся на період навчання в університеті. Я записався на курси від великої міжнародної ІТ-компанії, потім пішов на інтернатуру і вже у 25 років став Team Lead.

У 2021 році почалася нова сторінка моєї професійної історії — я приєднався до Р2Н. Прийшов як найперший спеціаліст з Java та незабаром долучився до команди своїх друзів. Разом ми почали працювати на першому Java-проєкті, який успішно ведемо до сьогодні.

У цій статті ми зібрали найактуальніші оновлення та покращення в Java 21, які допоможуть зробити кодування досконалішим та підвищити продуктивність. Розглянемо всі нові функції та зміни разом із короткими описами й прикладами, щоб це знайомство було швидким та ефективним.

Одразу після релізу Java 20 в березні 2023 року, 19 вересня цього ж року Oracle запустила версію Java 21. Вона, як наступний довгостроковий реліз підтримки (LTS) після Java 17, дає розробникам ще більше стабільності та нові інструменти для роботи.

Чому вам потрібно оновитись до Java 21

Послідовно розглянемо відповідь на це питання:

  • Java 21 отримуватиме довготривалу підтримку та оновлення, що гарантує стабільність і безпеку ваших застосунків протягом багатьох років.
  • Використовуйте вдосконалене поколіннєве збирання сміття для оптимізації продуктивності застосунків.
  • Новий API структурованого паралелізму робить управління паралельними завданнями простішим і ефективнішим, ідеально підходить для роботи зі складними моделями розпаралелювання.
  • Розробники отримують доступ до нового API для більш надійного та сучасного шифрування.
  • Нові функції, такі як шаблони записів, узгодження шаблонів та неіменовані шаблони й змінні, допомагають створювати компактний і легкий для розуміння код.

Мова та інструменти

Java 21 пропонує ряд вдосконалень у мовних функціях і інструментах, що спрямовані на спрощення кодування та покращення його читабельності. Серед ключових оновлень: шаблони записів, узгодження шаблонів для switch, а також попередні функції, такі як шаблони рядків, неіменовані шаблони, змінні, класи та методи основного екземпляра. Ці нововведення розширюють можливості мови, підвищують її гнучкість і зручність для розробників, що дозволяє оптимізувати процес написання коду та зробити його ефективнішим.

Давайте ж подивимось, як саме вони можуть стати нам у пригоді.

440: Шаблони записів

  • Розширення узгодження шаблонів для деструктуризації екземплярів класів записів, що дозволяє складніші запити до даних.
  • Додавання вкладених шаблонів, що дозволяє більш композиційні запити до даних.

До:

record Point(int x, int y) {}

static void printSum(Object obj) {
    if (obj instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x+y);
    }
}

Після:

static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}

441: Узгодження шаблонів для switch

Ми використовуємо його, щоб:

  • Розширити виразність та застосовність виразів і операторів switch, що дозволяє шаблонам з’являтися в мітках case.
  • Дозволити послаблення історичної ворожості switch до null, коли це потрібно.
  • Збільшити безпеку операторів switch, вимагаючи, щоб оператори switch з шаблонами охоплювали всі можливі вхідні значення.
  • Зробити так, щоб усі існуючі вирази та оператори switch продовжували компілюватися без змін і виконуватися з ідентичною семантикою.

До:

Object obj = 3.14;

if (obj == null) {
    System.out.println("Object is null");
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    if (i > 10) {
        System.out.println("Large Integer: " + i);
    } else {
           System.out.println("Small Integer: " + i);
    }
} else if (obj instanceof Double) {
       Double d = (Double) obj;
       System.out.println("Double: " + d);
} else {
       System.out.println("Unknown type");
}

Після:

Object obj = 3.14;

switch (obj) {
       case null -> System.out.println("Object is null");
       case Integer i && i > 10 -> System.out.println("Large Integer: " + i);
       case Integer i -> System.out.println("Small Integer: " + i);
       case Double d -> System.out.println("Double: " + d);
       default -> System.out.println("Unknown type");
}

430: Шаблони рядків (попередній перегляд)

  • Спрощення написання Java-програм завдяки простішому вираженню рядків, що містять значення, обчислювані під час виконання.
  • Покращення безпеки Java-програм, які складають рядки зі значень, наданих користувачем, і передають їх іншим системам (наприклад, будуючи запити до баз даних), підтримуючи перевірку та трансформацію як шаблону, так і значень його вбудованих виразів.
  • Збереження гнучкості, яка дозволяє Java-бібліотекам визначати синтаксис форматування, що використовується у шаблонах рядків.
  • Спрощене використання API, які приймають рядки, написані на не-Java мовах (наприклад, SQL, XML та JSON).
  • Можливість створення нестрокових значень, обчислених з літерального тексту та вбудованих виразів, без необхідності переходу через проміжне представлення рядка.

До:

String name = "John";
int age = 30;

// Using concatenation
String message = "Name: " + name + ", Age: " + age;
System.out.println(message);

// Using String.format
String formattedMessage = String.format("Name: %s, Age: %d", name, age);
System.out.println(formattedMessage);

Після:

String name = "John";
int age = 30;

// Using String Templates
String message = STR."Name: \{name}, Age: \{age}";
System.out.println(message);

443: Неіменовані шаблони та змінні (попередній перегляд)

  • Покращення читабельності шаблонів записів шляхом усунення непотрібних вкладених шаблонів.
  • Покращення підтримуваності усього коду через ідентифікацію змінних, які повинні бути оголошені (наприклад, у блоці catch), але не будуть використовуватися.

До:

Map<String, Object> map = Map.of("key1", 123, "key2", "value");

 for (var entry : map.entrySet()) {
    String key = entry.getKey();
    Object value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
} 

Після:

Map<String, Object> map = Map.of("key1", 123, "key2", "value");

for (var entry : map.entrySet()) {
    var (key, _) = entry;
    System.out.println("Key: " + key);
}

445: Неіменовані класи та інстанційні основні методи (попередній перегляд)

Чому:

  • Пропонує плавний вступ до Java так, що викладачі мають змогу вводити поняття програмування поступово.
  • Допомагає студентам писати базові програми лаконічно та поступово розвивати свій код у міру зростання їхніх навичок.
  • Зменшує формальності написання простих програм, таких як сценарії та утиліти командного рядка.
  • Не вводить окремий діалект Java для початківців.
  • Не вводить окремий набір інструментів для початківців; студентські програми повинні компілюватися та запускатися тими самими інструментами, які компілюють і запускають будь-яку іншу Java-програму.

До:

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

Після:

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

Бібліотеки

Бібліотеки в Java 21 значно покращені: це надає розробникам більш надійні та універсальні інструменти. Розгляньмо нові Sequenced Collections, API механізму ключового шифрування та інші важливі оновлення. Варто відзначити досягнення в API Foreign Function & Memory, Vector API, Structured Concurrency та Scoped Values, кожне з яких вносить унікальні можливості в екосистему Java.

431: Послідовні колекції

До 21 версії фреймворк колекцій Java не мав загальних типів, які б дозволяли розробникам визначати та працювати з порядком зустрічі елементів. Це обмеження ускладнювало виконання операцій, пов’язаних з порядком зустрічі, таких як, наприклад, обробка елементів у зворотному порядку. Процес був або громіздким, або неможливим. Тож введення послідовних колекцій розв’язує цю проблему, надаючи нові інтерфейси, які представляють колекції з визначеним порядком зустрічі.

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

Java 21 вводить три нових інтерфейси: SequencedCollection, SequencedSet і SequencedMap. Ці інтерфейси безперебійно інтегруються в чинну ієрархію типів наступним способом:

interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();
    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

Новий метод reversed особливо цікавий, оскільки він дає новий погляд на послідовну колекцію, що дозволяє інтегрувати через колекцію у зворотному напрямку, використовуючи звичайні підозрювані: цикли з посиленим for, явні цикли iterator(), forEach(), stream(), parallelStream() та toArray().

452: Механізм шифрування ключів API

Механізми шифрування ключів (KEM) — це методи, що захищають симетричні ключі за допомогою асиметричної криптографії. У Java 21 з’явився новий API, який спрощує використання алгоритмів KEM у високорівневих протоколах і криптографічних схемах.

Прикладами KEM є RSA-KEM (шифрування ключів на основі RSA) та ECIES (інтегрована схема шифрування на еліптичних кривих). Новий API дозволяє постачальникам безпеки реалізовувати алгоритми KEM як у Java, так і в нативному коді, забезпечуючи надійний захист симетричних ключів через криптографію з відкритим ключем.

442: API зовнішніх функцій та пам’яті (третій попередній перегляд)

Чому:

  • Простота у використанні: замінює Java Native Interface (JNI) передовою, чистою моделлю розробки на Java.
  • Продуктивність: забезпечує продуктивність, яка є порівнянною, якщо не кращою, ніж в існуючих API, таких як JNI та sun.misc.Unsafe.
  • Загальність: забезпечує способи роботи з різними типами зовнішньої пам’яті (наприклад, нативною пам’яттю, постійною пам’яттю та керованою динамічною пам’яттю) і, з часом, адаптацію до інших платформ (наприклад, 32-бітний x86) та зовнішніх функцій, написаних на інших мовах, крім C (наприклад, C++, Fortran).
  • Безпека: дозволяє програмам виконувати небезпечні операції з зовнішньою пам’яттю, але за замовчуванням попереджає користувачів про такі операції.

448: Векторний API (6-та інкубаційна фаза)

Векторний API досягнув своєї шостої фази в JDK 21. Вперше представлений у JDK 16, цей API пройшов кілька етапів інкубації в наступних релізах. Інкубація дає змогу отримати ранній зворотний зв’язок від користувачів щодо API, який ще не фіналізовано. Докладніше про інкубаційні модулі можна дізнатися на openjdk.org.

Цей API дозволяє виражати векторні обчислення, які надійно компілюються під час виконання, в оптимальні векторні інструкції на цільовій архітектурі центрального процесора. Він надає незалежний від платформи, зрозумілий і лаконічний API для вираження різноманітних векторних обчислень, які зазвичай включають послідовності векторних операцій у циклах з можливим потоком керування.

На архітектурах ЦП, що підтримуються, ці оптимальні векторні інструкції забезпечують вищу продуктивність порівняно з еквівалентними скалярними обчисленнями. Серед помітних удосконалень в інкубаційній фазі JDK 21 — додавання операції виключного АБО (xor) до векторних масок та покращення продуктивності векторних перестановок.

453: Структурований паралелізм (попередній перегляд)

Java 21 спрощує паралельне програмування завдяки API для структурованого паралелізму, концепція якого вже успішно застосовувалася в Python і C++. Цей API, представлений як попередня функція, дозволяє об’єднувати групи пов’язаних завдань, виконуваних у різних потоках, в єдину робочу одиницю. Це значно спрощує обробку помилок, скасування завдань і підвищує надійність та спостережуваність програм.

Структурований паралелізм забезпечує, що всі паралельні завдання, які почали виконуватись у рамках одного потоку керування, завершуються й об’єднуються в тому ж обсязі. Батьківське завдання очікує завершення дочірніх, ефективно використовуючи віртуальні потоки для управління ресурсами.

Приклад неструктурованого паралелізму

До Java 21 управління паралелізмом часто передбачало використання ExecutorService:

Response handle() throws ExecutionException, InterruptedException {
    Future<String> user = esvc.submit(() -> findUser());
    Future<Integer> order = esvc.submit(() -> fetchOrder());
    String theUser = user.get(); // Join findUser
    int theOrder = order.get(); // Join fetchOrder
    return new Response(theUser, theOrder);
}

Цей підхід може призвести до:

  • Витоку потоків, якщо завдання не вдається.
  • Непослідовного поширення скасування.
  • Непотрібного очікування завдань.

Приклад структурованого паралелізму

Структурований підхід до управління паралелізмом java.util.concurrent, реалізований у класі StructuredTaskScope в Java 21, вирішує:

Response handle() throws ExecutionException, InterruptedException { 
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {   
        Supplier<String> user = scope.fork(() -> findUser()); 
        Supplier<Integer> order = scope.fork(() -> fetchOrder()); 
        scope.join()             // Join both subtasks 
             .throwIfFailed();   // Propagate errors 
        return new Response(user.get(), order.get()); 
    } 
}

Має наступні переваги:

  • Обробка помилок: якщо одне з підзавдань завершується з помилкою, інше автоматично скасовується.
  • Поширення скасування: при перериванні обоє підзавдань будуть скасовані.
  • Зрозумілість: чітка структура з визначеною областю дії та автоматичним очищенням ресурсів.
  • Спостережуваність: дампи потоків відображають ієрархію завдань, що покращує процес налагодження.

Структурований паралелізм у Java 21 автоматизує управління завданнями, роблячи паралельне програмування більш надійним і зрозумілим.

446: Область видимості значень (попередній перегляд)

  • Простота використання: забезпечує модель програмування для спільного використання даних як всередині потоку, так і з дочірніми потоками, що спрощує розуміння потоку даних.
  • Зрозумілість: робить термін життя спільних даних видимим із синтаксичної структури коду.
  • Надійність: гарантує, що дані, якими ділиться викликаючий, можуть бути отримані лише легітимними викликаними.
  • Продуктивність: дозволяє спільним даним бути незмінними, щоб дозволити спільне використання багатьма потоками та увімкнути оптимізацію під час виконання.

Продуктивність та час виконання

439: Generational ZGC

Шляхом введення поколіннєвого збирання сміття для підвищення продуктивності застосунків, Java 21 покращує Z Garbage Collector (ZGC), особливістю якого є низька затримка та висока масштабованість.

Це оновлення дозволяє ZGC підтримувати окремі покоління об’єктів (наприклад, молоді та старі об’єкти). Частіше збираючи молоді об’єкти, оскільки саме вони найвірогідніше «умруть» швидше, поколіннєвий ZGC зменшує вимоги до процесора та додаткового використання пам’яті, а також допомагає уникнути зупинок у виділенні пам’яті.

Оскільки поколіннєвий ZGC не впливає на пропускну здатність, очікується, що він буде кращим рішенням для більшості випадків використання порівняно з непоколіннєвим ZGC. Згодом ця нова функція може врешті-решт повністю замінити стару версію.

444: Віртуальні потоки

Традиційна модель потоків Java може стати невиправдано «дорогою», якщо застосунок створює більше потоків, ніж операційна система (ОС) може ефективно керувати. Крім того, коли життєві цикли потоків короткі, додаткове використання пам’яті на створення потоків є доволі значними.

Віртуальні потоки розв’язують цю проблему шляхом відображення потоків Java на носії потоків, що керують операціями з потоками (тобто монтуванням та демонтажем) незалежно від потоків ОС. Це, своєю чергою, надає розробникам більшу гнучкість і контроль над управлінням потоками.

Це приклад, який порівнює віртуальні потоки з традиційними потоками ОС/платформи. Програма використовує ExecutorService для створення 10 000 завдань і очікує завершення всіх з них. «За лаштунками» JDK виконує ці завдання на обмеженій кількості носіїв і потоків ОС, дозволяючи вам ефективно і легко писати паралельний код.

Управління та підтримка

Забезпечення цілісності та розвитку платформи Java вимагає постійної підтримки й адаптації до сучасних вимог. У цьому розділі розглянемо підготовчі заходи щодо заборони динамічного завантаження агентів і відмови від підтримки 32-бітної x86-версії для Windows. Ці зміни є важливими кроками для узгодження Java із сучасними технологічними стандартами та вимогами безпеки.

451: Підготовка до заборони динамічного завантаження агентів

Для підвищення цілісності системи в майбутніх випусках Java буде заборонено динамічне завантаження агентів, які є компонентами, що можуть втручатися у код програми під час її виконання. Ці агенти часто використовуються для інструментації класів такими інструментами, як профайлери.

JDK 21 вимагає, щоб динамічне завантаження агентів було схвалено власником програми. Для підготовки до цієї зміни JDK 21 видаватиме попередження, коли агенти динамічно завантажуються в запущену JVM, готуючи ґрунт для цього обмеження в майбутніх випусках.

449: Відмова від 32-бітної x86-версії для Windows

Ера 32-бітної x86-версії для Windows добігає кінця. У JDK 21 відмовилися від цієї версії, позначивши її для видалення в майбутньому випуску. Цей крок узгоджується з графіком завершення життєвого циклу Windows 10, останньої ОС, яка підтримує 32-бітну розрядність. Цей процес завершить свій життєвий цикл у жовтні 2025 року.

Наразі під час спроби налаштувати збірку для Windows 32-bit x86 (x86-32) відображатиметься повідомлення про помилку, яке можна закрити.

Висновок

Щоб Java залишалася цілісною та розвивалася, її потрібно постійно вдосконалювати. Незабаром для динамічного завантаження агентів знадобиться схвалення власника програми — це значно підвищить безпеку. Також поступово припиняється підтримка 32-бітної x86-версії для Windows у рамках завершення підтримки Windows 10.

Ці зміни спрямовані на те, щоб Java залишалася сучасною, безпечною та готовою до майбутніх викликів і оновлень.

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному2
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Підписатись на коментарі