В одной из моих предыдущих статей я писал о фичах между LTS-версиями Java 17 и 21. Сегодня, два года спустя (Как?! Уже два года?!), выходит новый LTS-релиз — Java 25.

Подавляющее большинство проектов пропускают промежуточные релизы и используют только LTS-версии Java. Так что давайте посмотрим, какие возможности новая LTS-версия (Java 25) приносит по сравнению с предыдущей LTS-версией (Java 21).

В таблицах ниже я буду использовать следующие сокращения для обозначения состояния фич:

  • exp = Experimental;

  • inc[2|3|4|etc] = Incubator [2|3|4|etc];

  • pre[2|3|4|etc] = Preview [2|3|4|etc];

  • prod = Production;

  • w/d = Withdrawn.

Production-ready фичи в Java 25

Feature

Java 21

Java 22

Java 23

Java 24

Java 25

Scoped Values

JEP 446 (pre)

JEP 464 (pre2)

JEP 481 (pre3)

JEP 487 (pre4)

JEP 506 (prod)

Flexible Constructor Bodies

-

JEP 447 (pre)

JEP 482 (pre2)

JEP 492 (pre3)

JEP 513 (prod)

Unnamed Variables & Patterns

JEP 443 (pre)

JEP 456 (prod)

+

+

+

Stream Gatherers

-

JEP 461 (pre)

JEP 473 (pre2)

JEP 485 (prod)

+

Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism

-

-

-

JEP 496 (prod)

+

Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm

-

-

-

JEP 497 (prod)

+

Key Derivation Function API

-

-

-

JEP 478 (pre)

JEP 510 (prod)

Foreign Function & Memory API

JEP 442 (pre3)

JEP 454 (prod)

+

+

+

Compact Source Files and Instance Main Methods

JEP 445 (pre)

JEP 463 (pre2)

JEP 477 (pre3)

JEP 495 (pre4)

JEP 512 (prod)

Markdown Documentation Comments

-

-

JEP 467 (prod)

+

+

Module Import Declarations

-

-

JEP 476 (pre)

JEP 494 (pre2)

JEP 511 (prod)

Ahead-of-Time Class Loading & Linking

-

-

-

JEP 483 (prod)

+

Ahead-of-Time Command-Line Ergonomics

-

-

-

-

JEP 514 (prod)

Ahead-of-Time Method Profiling

-

-

-

-

JEP 515 (prod)

JFR Cooperative Sampling

-

-

-

-

JEP 518 (prod)

JFR Method Timing & Tracing

-

-

-

-

JEP 520 (prod)

Compact Object Headers

-

-

-

JEP 450 (exp)

JEP 519 (prod)

Prepare to Restrict the Use of JNI

-

-

-

JEP 472 (prod)

+

Class-File API

-

JEP 457 (pre)

JEP 466 (pre2)

JEP 484 (prod)

+

Permanently Disable the Security Manager

-

-

-

JEP 486 (prod)

+

Launch Multi-File Source-Code Programs

-

JEP 458 (prod)

+

+

+

Region Pinning for G1

-

JEP 423 (prod)

+

+

+

Late Barrier Expansion for G1

-

-

-

JEP 475 (prod)

+

ZGC: Generational Mode by Default

-

-

JEP 474 (prod)

+

+

ZGC: Remove the Non-Generational Mode

-

-

-

JEP 490 (prod)

+

Generational Shenandoah

-

-

-

JEP 404 (exp)

JEP 521 (prod)

Synchronize Virtual Threads without Pinning

-

-

-

JEP 491 (prod)

+

Linking Run-Time Images without JMODs

-

-

-

JEP 493 (prod)

+

Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

-

-

JEP 471 (prod)

+

+

Warn upon Use of Memory-Access Methods in sun.misc.Unsafe

-

-

-

JEP 498 (prod)

+

Remove the Windows 32-bit x86 Port

-

-

-

JEP 479 (prod)

+

Deprecate the 32-bit x86 Port for Removal

-

-

-

JEP 501 (prod)

+

Remove the 32-bit x86 Port

-

-

-

-

JEP 503 (prod)

Preview-фичи в Java 25

Feature

Java 21

Java 22

Java 23

Java 24

Java 25

Stable Values

-

-

-

-

JEP 502 (pre)

PEM Encodings of Cryptographic Objects

-

-

-

-

JEP 470 (pre)

Structured Concurrency

JEP 453 (pre)

JEP 462 (pre2)

JEP 480 (pre3)

JEP 499 (pre4)

JEP 505 (pre5)

Primitive Types in Patterns, instanceof, and switch

-

-

JEP 455 (pre)

JEP 488 (pre2)

JEP 507 (pre3)

Incubator фичи в Java 25

Feature

Java 21

Java 22

Java 23

Java 24

Java 25

Vector API

JEP 448 (inc6)

JEP 460 (inc7)

JEP 469 (inc8)

JEP 489 (inc9)

JEP 508 (inc10)

Экспериментальные фичи в Java 25

Feature

Java 21

Java 22

Java 23

Java 24

Java 25

JFR CPU-Time Profiling

-

-

-

-

JEP 509 (exp)

Отозванные фичи между Java 21 и Java 25

Feature

Java 21

Java 22

Java 23

Java 24

Java 25

String Templates

JEP 430 (pre)

JEP 459 (pre2)

JEP 465 (pre3 - w/d)

-

-

Production-Ready фичи с примерами

Теперь давайте кратко разберём некоторые фичи, готовые к использованию в проде, и посмотрим примеры кода.
Полный исходный код доступен на GitHub: pfilaretov42/java-features.

Scoped Values

Scoped Values предоставляют более безопасную альтернативу thread-local переменным, предлагая неизменяемые, наследуемые значения, доступные только внутри определённой области видимости.

До: подход с ThreadLocal

Словно ношение Единого Кольца — опасно, если доверить кому попало:

class ThreadLocalTest {
    private static final ThreadLocal<String> CURRENT_RING_BEARER = new ThreadLocal<>();

    void dangerousJourney() {
        CURRENT_RING_BEARER.set("Frodo");
        try {
            travelToMordor();
        } finally {
            CURRENT_RING_BEARER.remove();
        }
    }

    void travelToMordor() {
        // Any method in the call chain can access...
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");

        // ...and modify
        CURRENT_RING_BEARER.set("Sam");
        bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");
    }

    public static void main() {
        ThreadLocalTest test = new ThreadLocalTest();
        test.dangerousJourney();
    }
}

После: способ со ScopedValue

Подобно фиалу Галадриэль — свет, надёжно удерживаемый внутри:

public class ScopedValuesTest {
    private static final ScopedValue<String> CURRENT_RING_BEARER = ScopedValue.newInstance();

    void safeJourney() {
        ScopedValue.where(CURRENT_RING_BEARER, "Frodo")
                .run(this::travelToMordorSafely);
    }

    private void travelToMordorSafely() {
        // Only accessible in this scope, cannot be modified
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden safely");

        // Attempting to rebind would result in compilation error:
        //currentRingBearer.set("Sam");
    }

    void noJourney() {
        // Not accessible outside the scope, throws NoSuchElementException (ScopedValue not bound):
        String bearer = CURRENT_RING_BEARER.get();
        System.out.println(bearer + " bears the burden");
    }

    public static void main() {
        ScopedValuesTest test = new ScopedValuesTest();
        test.safeJourney();
        test.noJourney();
    }
}

Flexible Constructor Bodies

Позволяет более гибко располагать инструкции в теле конструктора, включая возможность размещать код перед явными вызовами других конструкторов (this() или super()).

До: жёсткие правила конструктора

Словно непоколебимые стены Ортанка — никакой гибкости:

class Palantir {
    private final String owner;
    private final boolean isCorrupted;

    public Palantir(String owner) {
        // This would not compile:
        //validateOwner(owner);
        // Call to this() must be the first statement:
        this(validateOwner(owner), false);
    }

    private static String validateOwner(String owner) {
        if (owner == null || owner.isBlank()) {
            throw new IllegalArgumentException("Owner cannot be null or blank");
        }
        System.out.println("A new palantir is given to " + owner);
        return owner;
    }

    private Palantir(String owner, boolean isCorrupted) {
        this.owner = owner;
        this.isCorrupted = isCorrupted;
    }
}

После: изящная свобода конструктора

Теперь струится, как воды Бруинена — естественно и свободно:

class Palantir {
    private final String owner;
    private final boolean isCorrupted;
    
    public Palantir(String owner) {
        validateOwner(owner);
        // Now this() call is allowed after other statements:
        this(owner, false);
    }

    private static void validateOwner(String owner) {
        if (owner == null || owner.isBlank()) {
            throw new IllegalArgumentException("Owner cannot be null or blank");
        }
        System.out.println("A new palantir is given to " + owner);
    }

    private Palantir(String owner, boolean isCorrupted) {
        this.owner = owner;
        this.isCorrupted = isCorrupted;
    }
}

Unnamed Variables & Patterns

Позволяет разработчикам явно отмечать неиспользуемые переменные и шаблоны с помощью символа подчёркивания (_), чтобы повысить читаемость кода.

Типичные случаи использования:

  • параметры исключений;

  • параметры лямбд;

  • переменные при pattern matching;

  • переменные в циклах, когда нужен только счётчик.

До: приходилось давать имена неиспользуемым переменным

Как если бы у каждого орка из Мордора был бейджик с именем:

try {
    int rings = forgeNewRing();
} catch (RingForgingException e) {  // Never used
    System.out.println("The fires of Mount Doom failed us!");
}

// Pattern matching with unused bindings
if (fighter instanceof Elf(String name, Weapon(String type, int damage))) {  // name and damage unused
    System.out.println("Armed with: " + type);
}

После: лаконичное объявление неиспользуемых переменных

Так же элегантно, как Леголас, попирающий законы гравитации:

try {
    int _ = forgeNewRing();
} catch (RingForgingException _) {  // Clear this is unused
    System.out.println("The fires of Mount Doom failed us!");
}

// Clean pattern matching
if (fighter instanceof Elf(_, Weapon(String type, _))) {
    System.out.println("Armed with: " + type);
}

Stream Gatherers

Представляет пользовательские промежуточные операции с помощью нового метода gather(Gatherer), который позволяет выполнять более сложные преобразования стримов, например, оконные функции.

До: ограничено встроенными операциями

Как попытка выковать Единое Кольцо, имея лишь простые инструменты:

List<String> hobbits = List.of("Frodo", "Sam", "Merry", "Pippin");

// To get overlapping pairs, we needed a workarounds
List<String> pairs = IntStream.range(0, hobbits.size() - 1)
        .mapToObj(i -> hobbits.get(i) + " & " + hobbits.get(i + 1))
        .toList();
System.out.println(pairs);
// Output: ["Frodo & Sam", "Sam & Merry", "Merry & Pippin"]

После: пользовательские преобразования стримов

Теперь под рукой могущество эльфийских кузнецов:

List<String> hobbits = List.of("Frodo", "Sam", "Merry", "Pippin");

Gatherer<String, ?, String> pairing = Gatherer.ofSequential(
        () -> new Object() { String previous; },
        (state, element, downstream) -> {
            if (state.previous != null) {
                downstream.push(state.previous + " & " + element);
            }
            state.previous = element;
            return true;
        }
);

List<String> pairs = hobbits.stream()
        .gather(pairing)
        .toList();
System.out.println(pairs);
// Output: ["Frodo & Sam", "Sam & Merry", "Merry & Pippin"]

Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism

Java 24 представляет встроенную поддержку механизма инкапсуляции ключей на основе модульных решёток (ML-KEM), который является частью квантово-устойчивой криптографии.

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

Её можно использовать, чтобы противостоять мощи квантовых компьютеров — будущей угрозе для классических RSA и Диффи–Хеллмана.

На примере обмена сессионными ключами...

// Elrond prepares his runes of protection (Receiver creates key pair)
ElrondTheReceiver elrond = new ElrondTheReceiver();

// Gandalf crafts a secret using Elrond’s rune (Sender uses receiver's public key to encapsulate session key)
GandalfTheSender gandalf = new GandalfTheSender(elrond.revealPublicRune());
SecretKey senderSessionKey = gandalf.getSessionKey();

// Elrond deciphers the sealed whisper from Gandalf (Receiver decapsulates to get the same session key)
SecretKey receiverSessionKey = elrond.decapsulateWhisper(gandalf.getSealedWhisper());
boolean secretsMatch = MessageDigest.isEqual(senderSessionKey.getEncoded(), receiverSessionKey.getEncoded());

// Output for verification:
HexFormat hex = HexFormat.of();
System.out.println("Sender session key:   " + hex.formatHex(senderSessionKey.getEncoded()));
System.out.println("Receiver session key: " + hex.formatHex(receiverSessionKey.getEncoded()));
System.out.println("Secrets match: " + secretsMatch);

if (secretsMatch) {
    // Gandalf and Elrond exchange messages using the securely transmitted session key
    // ...
}

...мы видим следующее.

До: передача ключей с помощью RSA

Словно держать в руках сталь Гондора — мощно, но не всесильно:

/**
 * Receiver generates key pair and decapsulates session key
 */
class ElrondTheReceiver {
    private final KeyPair keyPair;

    public ElrondTheReceiver() throws GeneralSecurityException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(4096);
        keyPair = generator.generateKeyPair();
    }

    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }

    public SecretKey decapsulateWhisper(byte[] sealedWhisper) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
        byte[] keyBytes = cipher.doFinal(sealedWhisper);

        return new SecretKeySpec(keyBytes, "AES");
    }
}
/**
 * Sender uses receiver's public key to generate session key
 */
class GandalfTheSender {
    private final SecretKey sessionKey; // Sender’s secret session key
    private final byte[] sealedWhisper; // The message with session key encapsulated for Receiver

    public GandalfTheSender(PublicKey receiverPublicKey) throws GeneralSecurityException {
        // Generate session key (AES)
        KeyGenerator generator = KeyGenerator.getInstance("AES");
        generator.init(256);
        sessionKey = generator.generateKey();

        // Encrypt (encapsulate) session key with RSA
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, receiverPublicKey);
        sealedWhisper = cipher.doFinal(sessionKey.getEncoded());
    }

    public SecretKey getSessionKey() {
        return sessionKey;
    }

    public byte[] getSealedWhisper() {
        return sealedWhisper;
    }
}

После: постквантовая инкапсуляция ключей

Подобно Вратам Мории — неприступны для любой силы:

/**
 * Receiver generates key pair and decapsulates session key
 */
class ElrondTheReceiver {
    private final KeyPair keyPair;

    public ElrondTheReceiver() throws GeneralSecurityException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("ML-KEM");
        keyPair = generator.generateKeyPair();
    }

    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }

    public SecretKey decapsulateWhisper(byte[] sealedWhisper) throws GeneralSecurityException {
        KEM kem = KEM.getInstance("ML-KEM");
        KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate());
        return decapsulator.decapsulate(sealedWhisper);
    }
}
/**
 * Sender uses receiver's public key to generate session key
 */
class GandalfTheSender {
    private final SecretKey sessionKey; // Sender’s secret session key
    private final byte[] sealedWhisper; // The message with session key encapsulated for Receiver

    public GandalfTheSender(PublicKey receiverPublicKey) throws GeneralSecurityException {
        KEM kem = KEM.getInstance("ML-KEM");
        KEM.Encapsulator encapsulator = kem.newEncapsulator(receiverPublicKey);
        KEM.Encapsulated encapsulated = encapsulator.encapsulate();

        sessionKey = encapsulated.key();
        sealedWhisper = encapsulated.encapsulation();
    }

    public SecretKey getSessionKey() {
        return sessionKey;
    }

    public byte[] getSealedWhisper() {
        return sealedWhisper;
    }
}

Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm

ML-DSA обеспечивает постквантовые защищённые цифровые подписи на основе криптографии на решётках и является частью нового криптографического набора Java для эпохи квантовых вычислений.

На примере подписи сообщений...

// Gandalf prepares his spell (Sender creates a public/private key pair)
GandalfTheSender gandalf = new GandalfTheSender();

// Sender signs a message using the private key
String scroll = gandalf.speakWordsOfPower();
byte[] waxSeal = gandalf.signMessage(scroll);

// Aragorn receives the scroll and the seal
AragornTheReceiver aragorn = new AragornTheReceiver(gandalf.revealPublicRune());

// Verifying the true words of Gandalf (Receiver verifies the message using the sender's public key)
boolean isTrueScroll = aragorn.verifyMessage(scroll, waxSeal);
System.out.println("Is the scroll valid: " + isTrueScroll); // true

// Attempt to fool the ranger with a forged scroll (verification fails for counterfeit message)
String fakeScroll = """
    A new Power is rising. Against it the old allies and policies will not avail us at all. \
    There is no hope left in Elves or dying Númenor.
    """;
isTrueScroll = aragorn.verifyMessage(fakeScroll, waxSeal);
System.out.println("Is the forged scroll valid: " + isTrueScroll); // false

...мы видим следующее.

До: подпись с помощью ECDSA

Словно стены Изенгарда перед гневом энтов:

/**
 * Sender signs message with private key
 */
class GandalfTheSender {
    private final KeyPair keyPair;

    public GandalfTheSender() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        generator.initialize(new ECGenParameterSpec("secp256r1"));
        keyPair = generator.generateKeyPair();
    }

    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }

    public byte[] signMessage(String message) throws Exception {
        Signature runeEngraver = Signature.getInstance("SHA256withECDSA");
        runeEngraver.initSign(keyPair.getPrivate());
        runeEngraver.update(message.getBytes());
        return runeEngraver.sign();
    }

    public String speakWordsOfPower() {
        return """
            It is not our part here to take thought only for a season, or for a few lives of Men, \
            or for a passing age of the world. We should seek a final end of this menace, \
            even if we do not hope to make one.
            """;
    }
}
/**
 * Receiver verifies the message with public key
 */
class AragornTheReceiver {
    private final PublicKey senderPublicKey;

    public AragornTheReceiver(PublicKey senderPublicKey) {
        this.senderPublicKey = senderPublicKey;
    }

    public boolean verifyMessage(String message, byte[] signature) throws Exception {
        Signature runeChecker = Signature.getInstance("SHA256withECDSA");
        runeChecker.initVerify(senderPublicKey);
        runeChecker.update(message.getBytes());
        return runeChecker.verify(signature);
    }
}

После: подпись с помощью постквантового ML-DSA

Словно оставляешь свой след в вечных залах Валинора:

/**
 * Sender signs message with private key
 */
class GandalfTheSender {
    private final KeyPair keyPair;

    public GandalfTheSender() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("ML-DSA");
        keyPair = generator.generateKeyPair();
    }

    public PublicKey revealPublicRune() {
        return keyPair.getPublic();
    }

    public byte[] signMessage(String message) throws Exception {
        Signature runeEngraver = Signature.getInstance("ML-DSA");
        runeEngraver.initSign(keyPair.getPrivate());
        runeEngraver.update(message.getBytes());
        return runeEngraver.sign();
    }

    public String speakWordsOfPower() {
        return """
            It is not our part here to take thought only for a season, or for a few lives of Men, \
            or for a passing age of the world. We should seek a final end of this menace, \
            even if we do not hope to make one.
            """;
    }
}
/**
 * Receiver verifies the message with public key
 */
class AragornTheReceiver {
    private final PublicKey senderPublicKey;

    public AragornTheReceiver(PublicKey senderPublicKey) {
        this.senderPublicKey = senderPublicKey;
    }

    public boolean verifyMessage(String message, byte[] signature) throws Exception {
        Signature runeChecker = Signature.getInstance("ML-DSA");
        runeChecker.initVerify(senderPublicKey);
        runeChecker.update(message.getBytes());
        return runeChecker.verify(signature);
    }
}

Key Derivation Function API

Эта фича представляет стандартизированный API для функций формирования ключей (KDF), заменяя разрозненные реализации единым интерфейсом для безопасного получения ключей.

До: ручное получение ключей

Как выковать клинок в грубых кузницах Мордора:

String password = "Mellon!";
byte[] salt = generateSalt();
int iterations = 65_536;
int keyLengthBits = 256;

// derive the key
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLengthBits);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey key = factory.generateSecret(spec);

System.out.println("Derived key: " + HexFormat.of().formatHex(key.getEncoded()));

После: API для генерации ключей

Теперь, подобно кузнецам Эрегиона, мы создаём ключи с высшей точностью:

KDF hkdf = KDF.getInstance("HKDF-SHA256");
byte[] password = "Mellon!".getBytes();
byte[] salt = generateSalt();
byte[] info = "Say 'Friend' and enter".getBytes();
int keyLengthBytes = 32;

// derive the key
AlgorithmParameterSpec params = HKDFParameterSpec.ofExtract()
        .addIKM(password)
        .addSalt(salt)
        .thenExpand(info, keyLengthBytes);
SecretKey key = hkdf.deriveKey("AES", params);

System.out.println("Derived key: " + HexFormat.of().formatHex(key.getEncoded()));

Foreign Function & Memory API

Заменяет Java Native Interface (JNI) более безопасным и эффективным способом вызова нативного кода и работы с off-heap памятью.

Для простоты я опущу названия пакетов и длинные пути к файлам. Полностью рабочий пример доступен на GitHub.

До: опасный подход через JNI

Словно Чёрная Речь Мордора — могущественно, но опасно...

Создаём класс ElvenScroll:

public class ElvenScroll {
    static {
        // Load the native library forged in C
        System.loadLibrary("mordor");
    }

    // Declare the native spell that reads the length of ancient text
    public native int countRunes(String ancientText);

    public static void main() {
        ElvenScroll scroll = new ElvenScroll();
        String runes = "Speak, friend, and enter";
        int length = scroll.countRunes(runes);
        System.out.println("Runes counted: " + length);
    }
}

Компилируем класс ElvenScroll с опцией -h:

javac -h . ElvenScroll.java

На выходе получается заголовочный файл ElvenScroll.h.

Создаём C-файл:

#include <jni.h>
#include <string.h>
#include "ElvenScroll.h"

JNIEXPORT jint JNICALL Java_ElvenScroll_countRunes(JNIEnv *env, jobject obj, jstring ancientText) {
    // Convert Elvish runes to C-compatible form
    const char *runes = (*env)->GetStringUTFChars(env, ancientText, NULL);
    if (runes == NULL) return 0;

    // Count the runes
    int length = (int)strlen(runes);

    // Release the spellbound memory
    (*env)->ReleaseStringUTFChars(env, ancientText, runes);
    return length;
}

Собираем C-библиотеку (команда ниже — для macOS):

gcc -shared -fpic -o libmordor.dylib \
-I ${JAVA_HOME}/include \
-I ${JAVA_HOME}/include/darwin \
mordor.c

На выходе получается файл libmordor.dylib.

Теперь запускаем класс ElvenScroll.

После: безопасный FFM API

Словно эльфийские мосты Лотлориэна — изящно и надёжно:

public class ElvenScroll {

    public static void main() throws Throwable {
        // Get a linker and a lookup object
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();

        // Get a handle to the foreign function ('strlen' from the C standard library)
        MethodHandle strlen = linker.downcallHandle(
            stdlib.find("strlen").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
        );

        try (Arena arena = Arena.ofConfined()) {
            // Allocate off-heap memory
            String runes = "Speak, friend, and enter";
            MemorySegment cString = arena.allocateFrom(runes);

            // Call the foreign function
            long runeCount = (long) strlen.invoke(cString);
            System.out.println("Runes counted: " + runeCount);
        }
        // Memory automatically freed here - no memory leaks!
        // No native code!
    }
}

Compact Source Files And Instance Main Methods

Упрощает синтаксис Java для начинающих, позволяя создавать однофайловые программы без шаблонного кода класса и предлагая более гибкие варианты объявления метода main().

До: многословное заклинание

Словно пространные и мудрёные речи Совета Элронда:

package dev.pfilaretov42.java25.csf_imm;

public class RingQuestBefore {
    public static void main(String[] args) {
        System.out.println("One does not simply walk into Mordor...");
    }
}

После: версия размером с хоббита

Словно короткое путешествие Бильбо:

// no package declaration
// no imports for classes in java.base module

void main() {
    IO.println("One does not simply walk into Mordor...");
}

Или с использованием instance-метода:

package dev.pfilaretov42.java25.csf_imm;

public class RingQuestAfterInstance {
    void main() {
        IO.println("One does not simply walk into Mordor...");
    }
}

Markdown Documentation Comments

Наконец-то! ? В Java появилась поддержка синтаксиса Markdown в комментариях Javadoc, что обеспечивает более удобочитаемую и поддерживаемую альтернативу HTML-тегам.

До: HTML-способ

Путь древних — громоздкий и тяжёлый, словно кузницы Изенгарда:

/**
* <h1>The One Ring</h1>
* <p>Forged by Sauron in the fires of Mount Doom.</p>
* <pre>{@code
* if (ring.isFound()) {
*     frodo.destroy(ring);
* }
* }</pre>
* <p><b>Warning:</b> Do not wear for extended periods.</p>
*/
public class OneRing {
  // ...
}

После: синтаксис Markdown

Теперь слова льются, словно эльфийская письменность Лотлориэна — аккуратно, красиво и точно:

/// # The One Ring
/// Forged by Sauron in the fires of Mount Doom.
/// ```java
/// if (ring.isFound()) {
///     frodo.destroy(ring);
/// }
/// ```
/// **Warning:** Do not wear for extended periods.
public class OneRing {
  // ...
}

Module Import Declarations

Вводит синтаксис import module, позволяющий разработчикам импортировать целые модули в обычных Java-файлах.

Используя следующие классы...

package lothlorien;

public class Galadriel {
    public void speakLightOfEärendil() {
        // ...
    }
}
package lothlorien;

public class Celeborn {
    public void offerCounsel() {
        // ...
    }
}
package lothlorien;

public class Haldir {
    public void escort() {
        // ...
    }
}

...и файл module-info.java...

module elves.of.lothlorien {
    exports lothlorien;
}

...вот что мы имеем.

До: объявления import

Словно бесконечные родословные Дома Финвэ:

import lothlorien.Celeborn;
import lothlorien.Galadriel;
import lothlorien.Haldir;

public class FarewellToLórien {
    void main() {
        Haldir guardian = new Haldir();
        guardian.escort();
      
        Celeborn lord = new Celeborn();
        lord.offerCounsel();

        Galadriel lady = new Galadriel();
        lady.speakLightOfEärendil();
    }
}

После: импорт модулей

Теперь всё организовано, словно в гномьем дворце:

import module elves.of.lothlorien;

public class FarewellToLórien {
    void main() {
        Haldir guardian = new Haldir();
        guardian.escort();
      
        Celeborn lord = new Celeborn();
        lord.offerCounsel();

        Galadriel lady = new Galadriel();
        lady.speakLightOfEärendil();
    }
}

Заключение

Всё ещё думаете, что за 30 лет Java устарела и стала скучной? Тогда взгляните на последнюю LTS-версию Java 25 — она приносит с собой множество классных фич!

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