JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · API · JDK 21 ADDITIONS

JDK 21 新增 API

JDK 21 · 速查主题 04 / 12 · 预计 24 分钟
核心24 分钟JEP 452
一句话JDK 21 除了虚拟线程和模式匹配这两条主线,还补了一批小而实用的 API:Math.clamp、String 的区间 indexOf、splitWithDelimiters、StringBuilder.repeat、Character 的 emoji 判断、KEM 密钥封装,以及让 HttpClient 支持 try-with-resources。

学完你要能自己回答

  1. Math.clamp 在 min > max 或 NaN 时分别是什么行为?
  2. String 的区间 indexOf 和「先 substring 再 indexOf」有什么区别?
  3. splitWithDelimiters 的 limit 参数如何影响结果?
  4. KEM 是加密算法吗?它和 TLS、密钥交换是什么关系?

Math.clamp:把值限制在区间内01

JDK 21 新增 Math.clamp,把值限制在 [min, max] 内。它比手写 Math.min(Math.max(v, min), max) 少一次拼写错误,也更清楚地表达意图。

重载返回说明
clamp(long value, int min, int max)int没有 int,int,int 重载;int 实参会走这个版本
clamp(long value, long min, long max)longlong 区间
clamp(float value, float min, float max)float浮点区间
clamp(double value, double min, double max)double浮点区间
System.out.println(Math.clamp(15, 0, 10));                  // 10
System.out.println(Math.clamp(-5, 0, 10));                  // 0
System.out.println(Math.clamp(5, 0, 10));                   // 5
System.out.println(Math.clamp(1_000_000_000_000L, 0L, 10L)); // 10

System.out.println(Math.clamp(Double.NaN, 0.0, 1.0));       // NaN
// Math.clamp(5, 10, 0);                                    // IllegalArgumentException
  • 区间是闭区间:等于 min 或 max 时原样返回。
  • NaN 原样返回 NaN,不会被夹到 min 或 max。
  • min > max 会抛 IllegalArgumentException,不会自动交换。
  • 浮点 -0.0 走 Math.max/min 语义时可能变成 +0.0,对符号敏感的场景不要依赖 clamp。
如果 min / max 来自配置,先把「min <= max」校验放在配置加载阶段。把非法区间留到请求路径上,只会在运行时炸。

String 区间查找:indexOf(str, begin, end)02

JDK 21 给 String 增加了带范围的 indexOf(String, int, int):只在 [beginIndex, endIndex) 内查找,返回绝对下标,找不到返回 -1。它避免了先 substring 再查找产生的临时字符串。

String s = "abcabc";
System.out.println(s.indexOf("bc", 0, 3)); // 1
System.out.println(s.indexOf("bc", 2, 6)); // 4
System.out.println(s.indexOf("bc", 0, 1)); // -1
System.out.println(s.indexOf("", 3, 3));   // 3 —— 空串在 begin 处命中

区间参数有严格约束:0 <= beginIndex <= endIndex <= length()。违反任何一条都抛 StringIndexOutOfBoundsException,而不是返回 -1。

String s = "abcdcef";
// s.indexOf("bc", 4, 2);  // begin > end
// s.indexOf("bc", -1, 3); // begin < 0
// s.indexOf("bc", 1, 99); // end > length
// 三者都抛 StringIndexOutOfBoundsException

System.out.println(s.indexOf("bc", 1, s.length())); // 1
// s.indexOf(null, 0, 2); // NullPointerException

注意:JDK 21 只增加了区间版 indexOf,没有区间版 lastIndexOf。要在区间内找最后一次出现,可以先用 lastIndexOf(str, end - 1) 找到,再检查是否落在 begin 之后。

String s = "abcabc";
int begin = 2, end = 6;
int i = s.lastIndexOf("bc", end - 1); // 4
boolean inRange = i >= begin;         // true
System.out.println(i + " / " + inRange);
区间越界抛异常是有意的契约:它把调用方的坐标错误立刻暴露出来,而不是悄悄返回 -1。写工具方法时不要把异常吞掉再转成「没找到」。

splitWithDelimiters:保留分隔符03

普通 split 只返回被切开的片段,分隔符丢失。JDK 21 的 splitWithDelimiters(regex, limit) 把匹配到的分隔符也作为独立元素保留下来,适合重建原文、解析简单表达式或保留格式。

System.out.println(Arrays.toString("a,b;c".splitWithDelimiters("[,;]", 0)));
// [a, ,, b, ;, c]

System.out.println(Arrays.toString("a,b;c".splitWithDelimiters("[,;]", 2)));
// [a, ,, b;c]

System.out.println(Arrays.toString("a,b;c".splitWithDelimiters("[,;]", 1)));
// [a,b;c]

System.out.println(Arrays.toString("".splitWithDelimiters(",", 0)));
// []
limit行为结果长度
0尽量切分,保留末尾空段与分隔符不限制
1完全不切分,返回整个输入1
2只应用一次分隔符,最后一段包含剩余未切分内容最多 2
n > 0最多产生 n 个元素,最后一段保留剩余内容最多 n

和 split 一样,分隔符参数是正则。按 .、|、* 等字符切分时要转义,或用 Pattern.quote。

splitWithDelimiters 不改变 split 的既有语义;普通 split 传入负 limit 时会保留尾部空段。需要重建原文时,用 String.join("", parts) 即可。

StringBuilder.repeat 与 Character emoji04

StringBuilder.repeat(CharSequence, int) 和 repeat(int codePoint, int count) 在 JDK 21 加入,直接在可变缓冲区里重复追加,不会先创建一个重复后的 String。

StringBuilder sb = new StringBuilder("ab");
sb.repeat("-", 3);
System.out.println(sb); // ab---

System.out.println(new StringBuilder().repeat('x', 3)); // xxx
System.out.println("[" + new StringBuilder().repeat("x", 0) + "]"); // []
// new StringBuilder().repeat("x", -1); // IllegalArgumentException

Character 新增了一组按码点判断 emoji 属性的方法。它们判断的是 Unicode 属性,不是「用户看到的一个字符」。

方法判断什么示例
isEmoji(int)是否是 emoji 字符😀 → true,'A' → false
isEmojiPresentation(int)是否默认以 emoji 形式呈现😀 → true
isEmojiModifier(int)是否是肤色修饰符🏻 → true
isEmojiModifierBase(int)是否能接肤色修饰符👍 → true
isEmojiComponent(int)是否是 emoji 组件(ZWJ、变体选择符等)ZWJ → true
isExtendedPictographic(int)是否是扩展象形字符😀 → true
System.out.println(Character.isEmoji(0x1F600));             // true
System.out.println(Character.isEmojiModifierBase(0x1F44D)); // true
System.out.println(Character.isEmojiComponent(0x200D));     // true
System.out.println(Character.isEmoji('A'));                 // false
emoji 序列(家庭、旗帜、带肤色的人)由多个码点组成。要按「用户感知的字符」切分,仍然需要 BreakIterator 或 ICU,不能只靠这些单码点判断。

KEM:标准的密钥封装接口05

JEP 452 引入 javax.crypto.KEM,把「用公钥封装出一个共享密钥」抽象成标准 API。它不是新的加密算法,而是让 DHKEM 这类密钥封装机制可以通过 JCA provider 统一调用。JDK 21 的 SunJCE 提供了 DHKEM。

KeyPair kp = KeyPairGenerator.getInstance("X25519").generateKeyPair();
KEM kem = KEM.getInstance("DHKEM");

// 发送方:用接收方公钥封装
KEM.Encapsulator encapsulator = kem.newEncapsulator(kp.getPublic());
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
byte[] ciphertext = encapsulated.encapsulation();
SecretKey senderKey = encapsulated.key();

// 接收方:用私钥解封装
KEM.Decapsulator decapsulator = kem.newDecapsulator(kp.getPrivate());
SecretKey receiverKey = decapsulator.decapsulate(ciphertext);

System.out.println(encapsulator.providerName()); // SunJCE
System.out.println(encapsulator.secretSize());   // 32
System.out.println(Arrays.equals(senderKey.getEncoded(), receiverKey.getEncoded())); // true
provider=SunJCE
secret size=32
encapsulation size=32
keys match=true
  • encapsulation() 是要传给接收方的密文;key() 是双方共享的密钥。
  • newEncapsulator 需要公钥,newDecapsulator 需要私钥;DHKEM 使用 X25519 密钥对。
  • 算法名由 provider 决定。换 provider 时,DHKEM 是否可用、参数要求都可能不同。
  • KEM 是协议积木,不是完整协议。TLS、SSH 等仍然应该使用完整协议实现。
不要把 KEM 和「加密任意数据」混为一谈:它只封装密钥。业务数据仍需要对称加密(如 AES-GCM),而密钥管理、认证和重放保护属于更高层协议。

HttpClient 支持 try-with-resources06

JDK 21 让 HttpClient 实现了 AutoCloseable,并新增 close()、shutdown()、shutdownNow()、awaitTermination()、isTerminated()。短生命周期的命令行工具可以用 try-with-resources 确保连接池和执行器被释放。

try (HttpClient client = HttpClient.newHttpClient()) {
    HttpRequest request = HttpRequest.newBuilder(
            URI.create("https://example.com/health")).GET().build();
    HttpResponse<String> response = client.send(
            request, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.statusCode());
}
// 离开 try 块后 client 已关闭,不能继续复用

HttpClient 仍然是线程安全、可复用的。长生命周期的服务不要每个请求 new 一个,也不要在请求结束后关闭共享客户端;只在应用关闭或工具退出时关闭。

JDK 21 的 HttpClient 最高支持 HTTP/2;HTTP/3 要到 JDK 26 才进入标准 HttpClient。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
Math.clamp21 正式int 实参走 clamp(long,int,int);NaN 原样返回,min>max 抛 IAE。
String.indexOf(String,int,int)21 正式只有 indexOf 有区间版,没有区间版 lastIndexOf。
String.splitWithDelimiters21 正式保留分隔符;limit 语义与 split 一致。
StringBuilder.repeat21 正式CharSequence 与 codePoint 两个重载。
Character emoji 判断21 正式按码点判断 Unicode 属性,不处理字素簇。
KEM API21 正式JEP 452;SunJCE 提供 DHKEM。
HttpClient AutoCloseable21 新增close / shutdown / awaitTermination。
FFM API21 预览,22 正式21 需要 --enable-preview 与 --enable-native-access。
Scoped Values21 预览,25 正式21 的 runWhere/callWhere/getWhere 在后续预览中被移除。
String Templates21 预览,23 撤回不要在生产代码中使用。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. Math.clamp(5, 10, 0) 会返回什么?
抛 IllegalArgumentException,因为 min > max。clamp 不会自动交换两个边界;NaN 则原样返回 NaN。
02. String 的区间 indexOf 在 begin > end 时返回 -1 吗?
不会。区间必须满足 0 <= begin <= end <= length(),否则抛 StringIndexOutOfBoundsException。找不到匹配才返回 -1。
03. splitWithDelimiters(",", 2) 与 limit=0 的结果有什么不同?
limit=0 会尽量切分并保留全部分隔符;limit=2 最多产生两个元素,只应用一次分隔符,最后一段包含剩余未切分的内容。
04. 为什么 Character.isEmoji 不能用来判断「一个用户看到的 emoji」?
它按单个码点判断 Unicode 的 Emoji 属性。一个 emoji 可能由多个码点组成(肤色、ZWJ、旗帜),用户感知的字符需要按字素簇切分。
05. KEM 能直接加密业务数据吗?
不能。KEM 只封装出一个共享密钥;业务数据还需要用对称加密(如 AES-GCM)处理,认证、重放保护和协议流程属于更高层。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。