Java语言特性系列
本文主要讲述一下Java21的新特性
java -version
openjdk version "21" 2023-09-19
OpenJDK Runtime Environment (build 21+35-2513)
OpenJDK 64-Bit Server VM (build 21+35-2513, mixed mode, sharing)
从version信息可以看出是build 21+35
在java21之前,字符串拼接或者字符串与表达式组合主要是用StringBuilder、String::format、java.text.MessageFormat,不过可读性都不是太好,java21引入了StringTemplate(java.lang.StringTemplate
)来解决这个问题。
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public interface StringTemplate {
List fragments();
List
StringTemplate是个接口,它定义了fragments、values、interpolate、process方法,同时提供了interpolate、process方法的默认实现;同时内置了两个processor,分别是STR和RAW,他们的区别在于RAW可以获取到StringTemplate类型,STR则是StringTemplate执行了interpolate方法之后的结果,获得到的是最终结果String;其基本语法就是用
\{}
来包含变量或者表达式
RAW示例
@Test
public void testRaw() {
int x = 10;
int y = 20;
StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
List fragments = st.fragments();
List
输出fragments:[, + , = , ], values:[10, 20, 30], st:10 + 20 = 30
STR示例
@Test
public void testStr() {
String name = "Joan";
String info = STR."My name is \{name}";
System.out.println(info);
}
输出My name is Joan
也支持方法调用和表达式
@Test
public void testStrExpression() {
String filePath = "tmp.dat";
File file = new File(filePath);
String msg = STR. "The file \{ filePath } \{ file.exists() ? "does" : "does not" } exist" ;
System.out.println(msg);
}
最后输出The file tmp.dat does not exist
对于还有格式化需求的,提供了java.util.FMT
@Test
public void testFmt() {
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
String table = FMT."""
Description Width Height Area
%-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()}
%-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()}
%-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()}
\{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()}
""";
System.out.println(table);
}
也可以自定义processor
@Test
public void testCustomProcessor() {
var MYJSON = StringTemplate.Processor.of(
(StringTemplate st) -> com.alibaba.fastjson.JSON.parseObject(st.interpolate())
);
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
JSONObject doc = MYJSON."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
System.out.println(doc);
}
java21引入了java.util.SequencedCollection、java.util.SequencedMap来统一各类集合的顺序方法方法
public interface SequencedCollection extends Collection {
SequencedCollection reversed();
default void addFirst(E e) {
throw new UnsupportedOperationException();
}
default void addLast(E e) {
throw new UnsupportedOperationException();
}
default E getFirst() {
return this.iterator().next();
}
default E getLast() {
return this.reversed().iterator().next();
}
default E removeFirst() {
var it = this.iterator();
E e = it.next();
it.remove();
return e;
}
default E removeLast() {
var it = this.reversed().iterator();
E e = it.next();
it.remove();
return e;
}
}
SequencedCollection继承了Collection接口,同时定义了reversed,提供了addFirst、addLast、getFirst、getLast、removeFirst、removeLast的default实现;List、SequencedSet接口都继承了SequencedCollection接口
public interface SequencedMap extends Map {
SequencedMap reversed();
default Map.Entry firstEntry() {
var it = entrySet().iterator();
return it.hasNext() ? new NullableKeyValueHolder<>(it.next()) : null;
}
default Map.Entry lastEntry() {
var it = reversed().entrySet().iterator();
return it.hasNext() ? new NullableKeyValueHolder<>(it.next()) : null;
}
default Map.Entry pollFirstEntry() {
var it = entrySet().iterator();
if (it.hasNext()) {
var entry = new NullableKeyValueHolder<>(it.next());
it.remove();
return entry;
} else {
return null;
}
}
default Map.Entry pollLastEntry() {
var it = reversed().entrySet().iterator();
if (it.hasNext()) {
var entry = new NullableKeyValueHolder<>(it.next());
it.remove();
return entry;
} else {
return null;
}
}
default V putFirst(K k, V v) {
throw new UnsupportedOperationException();
}
default V putLast(K k, V v) {
throw new UnsupportedOperationException();
}
default SequencedSet sequencedKeySet() {
class SeqKeySet extends AbstractMap.ViewCollection implements SequencedSet {
Collection view() {
return SequencedMap.this.keySet();
}
public SequencedSet reversed() {
return SequencedMap.this.reversed().sequencedKeySet();
}
public boolean equals(Object other) {
return view().equals(other);
}
public int hashCode() {
return view().hashCode();
}
}
return new SeqKeySet();
}
default SequencedCollection sequencedValues() {
class SeqValues extends AbstractMap.ViewCollection implements SequencedCollection {
Collection view() {
return SequencedMap.this.values();
}
public SequencedCollection reversed() {
return SequencedMap.this.reversed().sequencedValues();
}
}
return new SeqValues();
}
default SequencedSet> sequencedEntrySet() {
class SeqEntrySet extends AbstractMap.ViewCollection>
implements SequencedSet> {
Collection> view() {
return SequencedMap.this.entrySet();
}
public SequencedSet> reversed() {
return SequencedMap.this.reversed().sequencedEntrySet();
}
public boolean equals(Object other) {
return view().equals(other);
}
public int hashCode() {
return view().hashCode();
}
}
return new SeqEntrySet();
}
}
SequencedMap接口继承了Map接口,它定义了reversed方法,同时提供了firstEntry、lastEntry、pollFirstEntry、pollLastEntry、putFirst、putLast、sequencedKeySet、sequencedValues、sequencedEntrySet方法的默认实现
另外Collections还提供了工厂方法用于返回不可变类型
Collections.unmodifiableSequencedCollection(sequencedCollection)
Collections.unmodifiableSequencedSet(sequencedSet)
Collections.unmodifiableSequencedMap(sequencedMap)
ZGC分代回收无疑是一个重磅的GC特性,ZGC之前的版本不支持分代回收,此次支持分代回收的话,可以更方便地对年轻代进行收集,提高GC性能。目前是分代与非分代都支持,使用分代则通过-XX:+UseZGC-XX:+ZGenerational
开启,后续版本将会把分代设置为默认的,而-XX:-ZGenerational
用于开启非分代,最后将会废除非分代的支持,届时ZGenerational参数也就没有作用了。
JDK19的JEP 405: Record Patterns (Preview)将Record的模式匹配作为第一次preview
JDK20的JEP 432: Record Patterns (Second Preview)作为第二次preview
此次在JDK21则作为正式版本发布,使用示例如下
record Point(int x, int y) {}
// As of Java 21
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
// As of Java 21
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
ColoredPoint lr)) {
System.out.println(c);
}
}
在JDK14JEP 305: Pattern Matching for instanceof (Preview)作为preview
在JDK15JEP 375: Pattern Matching for instanceof (Second Preview)作为第二轮的preview
在JDK16JEP 394: Pattern Matching for instanceof转正
JDK17引入JEP 406: Pattern Matching for switch (Preview)
JDK18的JEP 420: Pattern Matching for switch (Second Preview)则作为第二轮preview
JDK19的JEP 427: Pattern Matching for switch (Third Preview)作为第三轮preview
JDK20的JEP 433: Pattern Matching for switch (Fourth Preview)作为第四轮preview
而此次JDK21将Pattern Matching for switch作为正式版本发布,示例如下
// Prior to Java 21
static String formatter(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
} else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
} else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
// As of Java 21
static String formatterPatternSwitch(Object obj) {
return 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();
};
}
// As of Java 21
static void testFooBarNew(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// As of Java 21
static void testStringEnhanced(String response) {
switch (response) {
case null -> { }
case "y", "Y" -> {
System.out.println("You got it");
}
case "n", "N" -> {
System.out.println("Shame");
}
case String s
when s.equalsIgnoreCase("YES") -> {
System.out.println("You got it");
}
case String s
when s.equalsIgnoreCase("NO") -> {
System.out.println("Shame");
}
case String s -> {
System.out.println("Sorry?");
}
}
}
// As of Java 21
static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) {
switch (c) {
case Suit.CLUBS -> {
System.out.println("It's clubs");
}
case Suit.DIAMONDS -> {
System.out.println("It's diamonds");
}
case Suit.HEARTS -> {
System.out.println("It's hearts");
}
case Suit.SPADES -> {
System.out.println("It's spades");
}
case Tarot t -> {
System.out.println("It's a tarot");
}
}
}
// As of Java 21
sealed interface Currency permits Coin {}
enum Coin implements Currency { HEADS, TAILS }
static void goodEnumSwitch1(Currency c) {
switch (c) {
case Coin.HEADS -> { // Qualified name of enum constant as a label
System.out.println("Heads");
}
case Coin.TAILS -> {
System.out.println("Tails");
}
}
}
static void goodEnumSwitch2(Coin c) {
switch (c) {
case HEADS -> {
System.out.println("Heads");
}
case Coin.TAILS -> { // Unnecessary qualification but allowed
System.out.println("Tails");
}
}
}
// As of Java 21
static void testNew(Object obj) {
switch (obj) {
case String s when s.length() == 1 -> ...
case String s -> ...
...
}
}
Foreign Function & Memory (FFM) API包含了两个incubating API
JDK14的JEP 370: Foreign-Memory Access API (Incubator)引入了Foreign-Memory Access API作为incubator
JDK15的JEP 383: Foreign-Memory Access API (Second Incubator)Foreign-Memory Access API作为第二轮incubator
JDK16的JEP 393: Foreign-Memory Access API (Third Incubator)作为第三轮,它引入了Foreign Linker API (JEP 389)
FFM API在JDK 17的JEP 412: Foreign Function & Memory API (Incubator)作为incubator引入
FFM API在JDK 18的JEP 419: Foreign Function & Memory API (Second Incubator)作为第二轮incubator
JDK19的JEP 424: Foreign Function & Memory API (Preview)则将FFM API作为preview API
JDK20的JEP 434: Foreign Function & Memory API (Second Preview)作为第二轮preview
JDK21则作为第三轮的preview,使用示例
.javac --release 21 --enable-preview ...java --enable-preview ...
// 1. Find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.ofConfined()) {
// 4. Allocate a region of off-heap memory to store four pointers
MemorySegment pointers
= offHeap.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 5. Copy the strings from on-heap to off-heap
for (int i = 0; i < javaStrings.length; i++) {
MemorySegment cString = offHeap.allocateUtf8String(javaStrings[i]);
pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 6. Sort the off-heap data by calling the foreign function
radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0');
// 7. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cString.getUtf8String(0);
}
} // 8. All off-heap memory is deallocated here
assert Arrays.equals(javaStrings,
new String[] {"car", "cat", "dog", "mouse"}); // true
Unnamed Patterns and Variables支持用_
来替代没有使用的变量声明,比如
r instanceof Point _
r instanceof ColoredPoint(Point(int x, int _), Color _)
if (r instanceof ColoredPoint(_, Color c)) { ... c ... }
switch (b) {
case Box(RedBall _), Box(BlueBall _) -> processBox(b);
case Box(GreenBall _) -> stopProcessing();
case Box(_) -> pickAnotherBox();
}
int acc = 0;
for (Order _ : orders) {
if (acc < LIMIT) {
... acc++ ...
}
}
while (q.size() >= 3) {
var x = q.remove();
var _ = q.remove();
var _ = q.remove();
... new Point(x, 0) ...
}
在JDK19[https://openjdk.org/jeps/425](JEP 425: Virtual Threads (Preview))作为第一次preview
在JDK20JEP 436: Virtual Threads (Second Preview)作为第二次preview,此版本java.lang.ThreadGroup被永久废弃
在JDK21版本,Virtual Threads正式发布,与之前版本相比,这次支持了threadlocal,然后也可以通过Thread.Builder来创建,而且也支持threaddump(jcmd
)Thread.dump_to_file -format=json
使用示例
void handle(Request request, Response response) {
var url1 = ...
var url2 = ...
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future1 = executor.submit(() -> fetchURL(url1));
var future2 = executor.submit(() -> fetchURL(url2));
response.send(future1.get() + future2.get());
} catch (ExecutionException | InterruptedException e) {
response.fail(e);
}
}
String fetchURL(URL url) throws IOException {
try (var in = url.openStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
一般用Executors.newVirtualThreadPerTaskExecutor()是想通过池化技术来减少对象创建开销,不过由于虚拟线程相比平台线程更为"廉价",因而不再需要池化,如果需要控制虚拟线程数则可以使用信号量的方式,因而提供了Thread.Builder来直接创建虚拟线程,示例如下:
Thread thread = Thread.ofVirtual().name("duke").unstarted(runnable);
Thread.startVirtualThread(Runnable)
未命名的类和实例main方法这个特性可以简化hello world示例,方便java新手入门,示例如下
static void main(String[] args) {
System.out.println("static main with args");
}
static void main() {
System.out.println("static main without args");
}
void main(String[] args) {
System.out.println("main with args");
}
void main() {
System.out.println("main with without args");
}
javac --release 21 --enable-preview Main.javajava --enable-preview Main
其中main方法选择的优先顺序是static的优于非static的,然后有args的优于没有args的
Scoped Values在JDK20的JEP 429: Scoped Values (Incubator)作为Incubator
此次在JDK21作为preview版本
ScopedValue是一种类似ThreadLocal的线程内/父子线程传递变量的更优方案。ThreadLocal提供了一种无需在方法参数上传递通用变量的方法,InheritableThreadLocal使得子线程可以拷贝继承父线程的变量。但是ThreadLocal提供了set方法,变量是可变的,另外remove方法很容易被忽略,导致在线程池场景下很容易造成内存泄露。ScopedValue则提供了一种不可变、不拷贝的方案,即不提供set方法,子线程不需要拷贝就可以访问父线程的变量。具体使用如下:
class Server {
public final static ScopedValue LOGGED_IN_USER = ScopedValue.newInstance();
private void serve(Request request) {
// ...
User loggedInUser = authenticateUser(request);
ScopedValue.where(LOGGED_IN_USER, loggedInUser)
.run(() -> restAdapter.processRequest(request));
// ...
}
}
通过ScopedValue.where可以绑定ScopedValue的值,然后在run方法里可以使用,方法执行完毕自行释放,可以被垃圾收集器回收
JDK16引入了JEP 338: Vector API (Incubator)提供了jdk.incubator.vector来用于矢量计算
JDK17进行改进并作为第二轮的incubatorJEP 414: Vector API (Second Incubator)
JDK18的JEP 417: Vector API (Third Incubator)进行改进并作为第三轮的incubator
JDK19的JEP 426:Vector API (Fourth Incubator)作为第四轮的incubator
JDK20的JEP 438: Vector API (Fifth Incubator)作为第五轮的incubator
而JDK21则作为第六轮的incubator,使用示例如下
static final VectorSpecies SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
废弃了对Windows 32-bit x86 (x86-32)的移植,以便后续版本删除
对将代理动态加载到正在运行的 JVM 中时发出警告,后续版本将不允许动态加载agent。
在 JDK 9 及更高版本中,可以通过
-XX:-EnableDynamicAgentLoading
禁止动态加载agent。
在 JDK 21 中,允许动态加载agent,但 JVM 会在发生时发出警告。例如:
WARNING: A {Java,JVM TI} agent has been loaded dynamically (file:/u/bob/agent.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
若要允许工具动态加载agent而不发出警告,用户必须在命令行上使用-XX:+EnableDynamicAgentLoading
Key Encapsulation Mechanism(KEM)是一种现代加密技术,它使用非对称或公钥加密来保护对称密钥。传统的方法是使用公钥加密一个随机生成的对称密钥,但这需要填充,并且可能难以证明安全。相反,KEM利用公钥的属性派生一个相关的对称密钥,这不需要填充。
此次新增了javax.crypto.KEM、javax.crypto.KEMSpi
package javax.crypto;
public class DecapsulateException extends GeneralSecurityException;
public final class KEM {
public static KEM getInstance(String alg)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, Provider p)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, String p)
throws NoSuchAlgorithmException, NoSuchProviderException;
public static final class Encapsulated {
public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params);
public SecretKey key();
public byte[] encapsulation();
public byte[] params();
}
public static final class Encapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
Encapsulated encapsulate();
Encapsulated encapsulate(int from, int to, String algorithm);
}
public Encapsulator newEncapsulator(PublicKey pk)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, SecureRandom sr)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, AlgorithmParameterSpec spec,
SecureRandom sr)
throws InvalidAlgorithmParameterException, InvalidKeyException;
public static final class Decapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException;
SecretKey decapsulate(byte[] encapsulation, int from, int to,
String algorithm)
throws DecapsulateException;
}
public Decapsulator newDecapsulator(PrivateKey sk)
throws InvalidKeyException;
public Decapsulator newDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException;
}
它主要是提供了newEncapsulator、newDecapsulator方法,使用示例如下
// Receiver side
KeyPairGenerator g = KeyPairGenerator.getInstance("ABC");
KeyPair kp = g.generateKeyPair();
publishKey(kp.getPublic());
// Sender side
KEM kemS = KEM.getInstance("ABC-KEM");
PublicKey pkR = retrieveKey();
ABCKEMParameterSpec specS = new ABCKEMParameterSpec(...);
KEM.Encapsulator e = kemS.newEncapsulator(pkR, specS, null);
KEM.Encapsulated enc = e.encapsulate();
SecretKey secS = enc.key();
sendBytes(enc.encapsulation());
sendBytes(enc.params());
// Receiver side
byte[] em = receiveBytes();
byte[] params = receiveBytes();
KEM kemR = KEM.getInstance("ABC-KEM");
AlgorithmParameters algParams = AlgorithmParameters.getInstance("ABC-KEM");
algParams.init(params);
ABCKEMParameterSpec specR = algParams.getParameterSpec(ABCKEMParameterSpec.class);
KEM.Decapsulator d = kemR.newDecapsulator(kp.getPrivate(), specR);
SecretKey secR = d.decapsulate(em);
// secS and secR will be identical
在JDK19的JEP 428: Structured Concurrency (Incubator)作为第一次incubator
在JDK20的JEP 437: Structured Concurrency (Second Incubator)作为第二次incubator
此次在JDK21则作为preview,使用示例如下
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier user = scope.fork(() -> findUser());
Supplier order = scope.fork(() -> fetchOrder());
scope.join() // Join both subtasks
.throwIfFailed(); // ... and propagate errors
// Here, both subtasks have succeeded, so compose their results
return new Response(user.get(), order.get());
}
}
上面列出的是大方面的特性,除此之外还有一些api的更新及废弃,主要见JDK 21 Release Notes,这里举几个例子。
Java21主要有如下几个特性
其中JEP 439: Generational ZGC及JEP 444: Virtual Threads应属于重磅级的特性,而JEP 430: String Templates (Preview)、JEP 431: Sequenced Collections、JEP 440: Record Patterns及JEP 441: Pattern Matching for switch则在语言表达力层面上有了增强
另外java21是继JDK 17之后最新的长期支持(LTS)版本,将获得至少8年的支持。