В одной из моих предыдущих статей я писал о фичах между 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
Экспериментальные фичи в Java 25
Feature |
Java 21 |
Java 22 |
Java 23 |
Java 24 |
Java 25 |
---|---|---|---|---|---|
JFR CPU-Time Profiling |
- |
- |
- |
- |
JEP 509 (exp) |
Отозванные фичи между Java 21 и Java 25
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 — она приносит с собой множество классных фич!