JDK 13 新特性详解

JDK 13 于 2019年9月17日 正式发布,是 JDK 9 之后延续半年发布节奏的第三个非 LTS 版本。JDK 13 虽然不包含像 JDK 12 那样引入预览特性的震撼力,但在 Text Blocks(文本块) 预览、Socket API 重写、ZGC 内存回收、动态 CDS 归档 等方面带来了实质性的改进,为后续 LTS 版本(JDK 17/21)奠定了重要基础。


目录

  1. 文本块 Text Blocks(预览)
  2. Switch 表达式增强(第二次预览)
  3. 动态 CDS 归档
  4. ZGC 未使用内存回收
  5. Socket API 重写
  6. String.formatted() 方法
  7. FileSystems 新工厂方法
  8. NIO ByteBuffer 增强
  9. JVM Constants API 更新
  10. SSL/TLS 增强
  11. javax.swing 增强
  12. JDK 13 特性总览表

1. 文本块 Text Blocks(预览)

1.1 概述

JEP 355 — Text Blocks(文本块)是 JDK 13 最重要的语言特性,以预览形式引入。文本块是一种多行字符串字面量,可以省去大量转义字符和字符串拼接,让嵌入在 Java 代码中的 HTML、JSON、SQL、XML 等片段变得可读性极强。

语法:使用 """ 三个双引号作为界定符,内容自动保留换行和缩进。

"""
第一行
第二行
第三行
"""

核心规则:

  • 开头 """ 必须在行尾(后面只能跟空白字符和换行符)
  • 结尾 """ 决定缩进基准(内容会相对此基准进行去缩进)
  • 内容中的 " 不需要转义,"" 也不需要,只有连续三个 """ 才需要转义
  • 支持 \n、\t、\s 等转义,新增 \ 行尾续行符(忽略换行)

1.2 代码案例

// ============ 1. 基础文本块 ============

// 传统写法(难以阅读)
String json_old = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"age\": 25,\n" +
    "  \"city\": \"Beijing\"\n" +
    "}";

// 文本块写法(清晰直观)
String json = """
    {
      "name": "Alice",
      "age": 25,
      "city": "Beijing"
    }
    """;
System.out.println(json);
// 输出:
// {
//   "name": "Alice",
//   "age": 25,
//   "city": "Beijing"
// }

// ============ 2. HTML 片段 ============

String html = """
    <!DOCTYPE html>
    <html>
      <head>
        <title>示例页面</title>
      </head>
      <body>
        <h1>Hello, Text Blocks!</h1>
        <p>这是一个多行字符串的示例。</p>
      </body>
    </html>
    """;

// ============ 3. SQL 查询 ============

String sql = """
    SELECT u.id, u.name, u.email, o.order_id, o.total
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.status = 'ACTIVE'
      AND o.created_at > '2024-01-01'
    ORDER BY o.total DESC
    LIMIT 100
    """;

// ============ 4. 缩进规则 ============

// 结尾 """ 的位置决定了去缩进的基准
// 内容中每行相对于 """ 的缩进会被保留

String indent1 = """
    Line A
      Line B(缩进2个空格相对基准)
    Line C
    """;
// 结果:
// Line A
//   Line B(缩进2个空格相对基准)
// Line C

// 如果结尾 """ 放在内容中间,会影响去缩进
String indent2 = """
    Line A
      Line B
    Line C""";
// 结果相同(""" 在行尾或内容末尾效果一样)

// ============ 5. 转义字符 ============

// \ 行尾续行符(忽略换行,拼接下一行)
String noNewline = """
    Hello \
    World""";
// 结果:"Hello World"(中间没有换行)

// \s 表示空格(用于保持行尾空格)
String trailingSpace = """
    Line A\s
    Line B""";
// "Line A " 末尾有一个空格(\s 转义为空格)

// \n 显式换行
String explicitNewline = """
    Line A\n
    Line B""";
// "Line A\n\nLine B"(多了一个空行)

// 双引号不需要转义
String quotes = """
    She said "Hello" to me.
    """;
// 正常输出:She said "Hello" to me.

// 三个连续双引号需要转义
String tripleQuote = """
    这里是三个连续双引号:\"""
    """;

// ============ 6. 文本块与 String 方法结合 ============

String template = """
    SELECT * FROM %s WHERE id = %d
    """;
String result = String.format(template, "users", 42);
// "SELECT * FROM users WHERE id = 42\n"

// 配合 formatted() 方法(JDK 13 同时新增)
String result2 = """
    SELECT * FROM %s WHERE id = %d
    """.formatted("users", 42);

// ============ 7. 实际应用场景 ============

// JSON 构建
String buildUserJson(String name, int age, String email) {
    return """
        {
          "name": "%s",
          "age": %d,
          "email": "%s"
        }
        """.formatted(name, age, email);
}

// 正则表达式(更清晰)
String regex = """
    \\d{4}-\\d{2}-\\d{2}  # 日期格式 YYYY-MM-DD
    """;

// 多行日志消息
String logMessage = """
    [ERROR] 数据库连接失败
    主机: db-server-01
    端口: 3306
    原因: 连接超时
    重试次数: 3
    """;

1.3 编译与运行

文本块在 JDK 13 中是预览特性,需要额外编译参数:

# 编译
javac --enable-preview --release 13 App.java

# 运行
java --enable-preview -cp . App

2. Switch 表达式增强(第二次预览)

2.1 概述

JEP 354 — Switch Expressions 在 JDK 12(JEP 325)首次以预览形式引入,JDK 13 中进行了第二次预览。主要变化是引入了 yield 关键字 替代 break 来从 switch 表达式中返回值,使语义更清晰。

JDK 12 vs JDK 13 对比:

  • JDK 12:使用 break 返回值(与 break 语句语义冲突)
  • JDK 13:使用 yield 返回值(新关键字,语义明确)

2.2 代码案例

// ============ 1. yield 关键字(JDK 13 新增) ============

// JDK 12 写法(已废弃)
// int numLetters = switch (day) {
//     case MONDAY, FRIDAY, SUNDAY: break 6;
//     case TUESDAY: break 7;
//     ...
// };

// JDK 13 写法(使用 yield)
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

int numLetters = switch (Day.MONDAY) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case WEDNESDAY, THURSDAY    -> 9;
    case SATURDAY               -> 8;
};
System.out.println(numLetters); // 6

// ============ 2. yield 用于冒号形式 ============

// 当使用传统冒号形式(:)时,需要用 yield 返回值
String describe = switch ("JAVA") {
    case "JAVA":
        yield "Java 编程语言";  // 使用 yield 返回
    case "PYTHON":
        yield "Python 编程语言";
    default:
        yield "未知语言";
};
System.out.println(describe); // "Java 编程语言"

// ============ 3. yield vs break 对比 ============

// JDK 12(旧):break 既用于跳出 switch 又用于返回值,语义混乱
// case A: break 1; // break 有两个含义

// JDK 13(新):yield 专门用于返回值,break 只用于跳出
// case A: yield 1; // 语义清晰

// ============ 4. yield 与标签 break 配合 ============

// 在冒号形式中,yield 可以在循环或嵌套 switch 中使用
String result = switch ("test") {
    case "test":
        StringBuilder sb = new StringBuilder();
        for (char c : "HELLO".toCharArray()) {
            if (c == 'L') break; // 这里的 break 是跳出 for 循环
            sb.append(c);
        }
        yield sb.toString(); // yield 返回 switch 表达式的值
    default:
        yield "default";
};
System.out.println(result); // "HE"

// ============ 5. 箭头形式不需要 yield ============

// 箭头形式(->)直接返回表达式值,不需要 yield
int result2 = switch ("B") {
    case "A" -> 1;  // 直接返回
    case "B" -> 2;  // 直接返回
    default -> 0;
};

// 箭头形式 + 代码块,需要用 yield
int result3 = switch ("B") {
    case "A" -> {
        // 做一些处理
        yield 1;
    }
    case "B" -> {
        yield 2;
    }
    default -> {
        yield 0;
    }
};

// ============ 6. 实际应用场景 ============

// HTTP 状态码描述
String httpStatus(int code) {
    return switch (code) {
        case 200 -> "OK";
        case 201 -> "Created";
        case 204 -> "No Content";
        case 400 -> "Bad Request";
        case 401 -> "Unauthorized";
        case 403 -> "Forbidden";
        case 404 -> "Not Found";
        case 500 -> "Internal Server Error";
        case 502 -> "Bad Gateway";
        case 503 -> "Service Unavailable";
        default -> "Unknown Status";
    };
}

// 复杂业务逻辑
String classify(int score) {
    return switch (score / 10) {
        case 10, 9 -> {
            String grade = "A";
            yield grade + "(优秀)";
        }
        case 8 -> {
            String grade = "B";
            yield grade + "(良好)";
        }
        case 7 -> {
            String grade = "C";
            yield grade + "(中等)";
        }
        case 6 -> {
            String grade = "D";
            yield grade + "(及格)";
        }
        default -> {
            yield "F(不及格)";
        }
    };
}
System.out.println(classify(85)); // "B(良好)"

3. 动态 CDS 归档

3.1 概述

JEP 350 — Dynamic Class-Data Sharing(动态类数据共享)。CDS 是 JDK 5 引入的技术,用于在多个 JVM 之间共享类元数据,减少启动时间和内存占用。JDK 13 之前,创建 CDS 归档需要手动执行多个步骤(生成类列表、创建归档)。JDK 13 实现了动态创建 CDS 归档,无需手动操作。

改进点:

  • 旧版 CDS(AppCDS,JDK 10):需要三次运行(dump class list → create archive → use archive)
  • JDK 13 动态 CDS:在应用退出时自动创建归档(-XX:ArchiveClassesAtExit),下次启动直接加载(-XX:SharedArchiveFile)

3.2 使用方式

# ============ 1. 创建动态 CDS 归档 ============

# 第一次运行:执行应用并生成归档
java -XX:ArchiveClassesAtExit=app-cds.jsa -jar myapp.jar

# 后续运行:加载归档启动
java -XX:SharedArchiveFile=app-cds.jsa -jar myapp.jar

# ============ 2. 传统 CDS 流程(JDK 10,对比) ============

# 步骤1:生成类列表
java -XX:DumpLoadedClassList=classes.lst -jar myapp.jar

# 步骤2:创建归档
java -Xshare:dump -XX:SharedClassListFile=classes.lst \
     -XX:SharedArchiveFile=classes.jsa -jar myapp.jar

# 步骤3:使用归档
java -Xshare:on -XX:SharedArchiveFile=classes.jsa -jar myapp.jar

# ============ 3. 动态 CDS 的优势 ============

# 1. 简化流程:从3步减少到2步
# 2. 自动包含应用类:不需要手动生成类列表
# 3. 归档内容更完整:包含运行时实际加载的所有类

3.3 实际应用场景

# 微服务启动优化
# 第一次:生成归档
java -XX:ArchiveClassesAtExit=service.jsa \
     -XX:SharedClassListFile=service-classes.lst \
     -jar microservice.jar

# 后续启动(启动时间减少 20%-40%)
java -XX:SharedArchiveFile=service.jsa \
     -jar microservice.jar

# Docker 容器中使用
# Dockerfile 示例:
# RUN java -XX:ArchiveClassesAtExit=/app/cds.jsa -jar /app/app.jar || true
# CMD java -XX:SharedArchiveFile=/app/cds.jsa -jar /app/app.jar

4. ZGC 未使用内存回收

4.1 概述

JEP 351 — ZGC(Z Garbage Collector)在 JDK 11(JEP 333)作为实验性特性引入,JDK 13 增强了 ZGC,使其能够将未使用的堆内存归还给操作系统。

核心改进:

  • ZGC 之前:堆内存一旦分配给 JVM,即使未使用也不会归还 OS
  • ZGC 之后(JDK 13):自动检测未使用内存并归还给操作系统
  • 对延迟无影响:内存回收操作不会增加 GC 停顿时间

适用场景:

  • 容器化部署(Docker/K8s):内存按需使用,避免资源浪费
  • 长时间运行的应用:内存使用量会波动,空闲时归还 OS
  • 多应用共享物理机:提高资源利用率

4.2 代码案例

// ============ 1. 启用 ZGC ============

// 命令行参数
// java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar

// ============ 2. 内存归还演示 ============

public class ZGCMemoryDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("初始堆内存使用情况:");
        printMemoryUsage();

        // 分配大量内存
        System.out.println("\n分配大量临时数据...");
        List<byte[]> buffers = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            buffers.add(new byte[10 * 1024 * 1024]); // 10MB × 100 = 1GB
        }
        System.out.println("分配后堆内存使用情况:");
        printMemoryUsage();

        // 释放引用
        System.out.println("\n释放引用,等待 GC...");
        buffers.clear();
        buffers = null;

        // 触发 GC
        System.gc();
        Thread.sleep(2000);

        System.out.println("GC 后堆内存使用情况:");
        printMemoryUsage();
        // ZGC 会将未使用的内存归还给 OS
        // 可以看到堆使用量显著下降
    }

    static void printMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        long max = runtime.maxMemory() / 1024 / 1024;
        long total = runtime.totalMemory() / 1024 / 1024;
        long free = runtime.freeMemory() / 1024 / 1024;
        long used = total - free;

        System.out.printf("  最大堆: %d MB%n", max);
        System.out.printf("  已分配: %d MB%n", total);
        System.out.printf("  已使用: %d MB%n", used);
        System.out.printf("  空闲:   %d MB%n", free);
    }
}

// ============ 3. 容器环境下的配置 ============

// Docker 中配合 -XX:MaxRAMPercentage 使用
// docker run -m 2g java \
//   -XX:+UnlockExperimentalVMOptions \
//   -XX:+UseZGC \
//   -XX:MaxRAMPercentage=75.0 \
//   -jar app.jar
//
// 效果:
// - 容器限制 2GB 内存
// - ZGC 最大使用 1.5GB(75%)
// - 空闲时自动归还内存给 OS
// - 其他容器可以使用剩余内存

5. Socket API 重写

5.1 概述

JEP 353 — 重新实现旧版 Socket API。java.net.Socket、ServerSocket、DatagramSocket 等类的底层实现是 1990 年代编写的,代码复杂、难以维护,且存在缓冲区溢出等安全隐患。JDK 13 用全新的实现替代了旧实现。

改进点:

  • 新实现更简洁、更易维护
  • 使用 Java NIO 的底层机制
  • 更好的错误处理
  • 为未来网络编程改进奠定基础
  • 默认使用新实现,可通过 -Djdk.net.useOldImpl 回退

注意:这是底层实现替换,API 不变,现有代码无需修改。

5.2 代码案例

// ============ 1. Socket API 使用(不变) ============

// 客户端
try (Socket socket = new Socket("localhost", 8080)) {
    OutputStream out = socket.getOutputStream();
    out.write("Hello Server".getBytes());

    InputStream in = socket.getInputStream();
    byte[] buffer = new byte[1024];
    int len = in.read(buffer);
    System.out.println("收到响应: " + new String(buffer, 0, len));
}

// ============ 2. ServerSocket(不变) ============

try (ServerSocket serverSocket = new ServerSocket(8080)) {
    System.out.println("服务器启动,监听端口 8080");

    while (true) {
        Socket client = serverSocket.accept();
        // 处理客户端连接
        new Thread(() -> {
            try (client) {
                InputStream in = client.getInputStream();
                byte[] buffer = new byte[1024];
                int len = in.read(buffer);
                System.out.println("收到: " + new String(buffer, 0, len));

                OutputStream out = client.getOutputStream();
                out.write("Hello Client".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

// ============ 3. DatagramSocket(不变) ============

// UDP 发送
try (DatagramSocket socket = new DatagramSocket()) {
    String message = "Hello UDP";
    byte[] data = message.getBytes();
    InetAddress address = InetAddress.getByName("localhost");
    DatagramPacket packet = new DatagramPacket(data, data.length, address, 9999);
    socket.send(packet);
}

// UDP 接收
try (DatagramSocket socket = new DatagramSocket(9999)) {
    byte[] buffer = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    socket.receive(packet);
    System.out.println("收到: " + new String(packet.getData(), 0, packet.getLength()));
}

// ============ 4. 回退到旧实现(如需要) ============

// 如果新实现出现问题,可以通过系统属性回退
// java -Djdk.net.useOldImpl=true -jar app.jar

// ============ 5. 新实现的底层改进 ============

// 1. 使用 NIO 的 SocketChannel 作为底层
// 2. 更好的线程安全性
// 3. 更清晰的错误码映射
// 4. 移除了旧实现中的 native 代码依赖

// 性能对比(概念性)
// 旧实现:直接调用 BSD socket API(C 代码)
// 新实现:通过 NIO 的 UnixDomainSocket 等抽象层

6. String.formatted() 方法

6.1 概述

JDK 13 为 String 类新增了 formatted(Object... args) 实例方法,等价于 String.format(this, args),让字符串格式化更加直观和流畅。

6.2 代码案例

// ============ 1. 基础用法 ============

// 传统写法
String s1 = String.format("Hello, %s! You are %d years old.", "Alice", 25);

// JDK 13 新写法(更直观)
String s2 = "Hello, %s! You are %d years old.".formatted("Alice", 25);
// "Hello, Alice! You are 25 years old."

// ============ 2. 与文本块配合(最佳搭档) ============

// 文本块 + formatted() 是绝配
String userJson = """
    {
      "name": "%s",
      "age": %d,
      "email": "%s",
      "active": %b
    }
    """.formatted("Alice", 25, "alice@example.com", true);

System.out.println(userJson);
// {
//   "name": "Alice",
//   "age": 25,
//   "email": "alice@example.com",
//   "active": true
// }

// ============ 3. SQL 模板 ============

String sql = """
    SELECT * FROM users
    WHERE name LIKE '%s'
      AND age BETWEEN %d AND %d
      AND status = '%s'
    ORDER BY created_at DESC
    LIMIT %d
    """.formatted("%Alice%", 18, 35, "ACTIVE", 50);

// ============ 4. 日志格式化 ============

String logEntry = "[%s] [%s] [%s] %s".formatted(
    java.time.LocalDateTime.now(),
    "INFO",
    "UserService",
    "User login successful: alice@example.com"
);

// ============ 5. 数字格式化 ============

String price = "商品价格: ¥%,.2f".formatted(1234567.89);
// "商品价格: ¥1,234,567.89"

String percent = "完成率: %.1f%%".formatted(85.5);
// "完成率: 85.5%"

// ============ 6. 与文本块结合的实际场景 ============

// HTML 模板
String htmlTemplate = """
    <!DOCTYPE html>
    <html>
    <head><title>%s</title></head>
    <body>
      <h1>%s</h1>
      <p>欢迎, %s! 您有 %d 条未读消息。</p>
    </body>
    </html>
    """.formatted("消息中心", "消息中心", "Alice", 5);

// 配置文件模板
String configTemplate = """
    server:
      port: %d
      host: %s
    database:
      url: jdbc:mysql://%s:%d/%s
      pool-size: %d
    """.formatted(8080, "0.0.0.0", "localhost", 3306, "mydb", 20);

7. FileSystems 新工厂方法

7.1 概述

JDK 13 为 java.nio.file.FileSystems 类新增了工厂方法,简化创建自定义文件系统实例的过程。

7.2 代码案例

// ============ 1. 新增的工厂方法 ============

// JDK 13 新增:
// FileSystems.newFileSystem(Path path, Map<String, ?> env)
// FileSystems.newFileSystem(Path path, ClassLoader classLoader)

// ============ 2. 创建 ZIP/JAR 文件系统 ============

import java.nio.file.*;
import java.net.URI;
import java.util.Map;

// 创建 ZIP 文件系统(旧方式)
Path zipPath = Path.of("example.zip");
URI uri = URI.create("jar:file:" + zipPath.toUri().getPath());

// JDK 13 新方式(更简洁)
Map<String, String> env = Map.of("create", "true");
try (FileSystem zipFs = FileSystems.newFileSystem(zipPath, env)) {
    // 在 ZIP 中创建文件
    Path entry = zipFs.getPath("README.txt");
    Files.writeString(entry, "Hello from ZIP filesystem!");

    Path dir = zipFs.getPath("data");
    Files.createDirectory(dir);
    Path dataFile = dir.resolve("config.json");
    Files.writeString(dataFile, """
        {
          "version": "1.0",
          "name": "example"
        }
        """);
}

// ============ 3. 读取 ZIP 文件内容 ============

Path zipFile = Path.of("archive.zip");
try (FileSystem zipFs = FileSystems.newFileSystem(zipFile, (ClassLoader) null)) {
    // 遍历 ZIP 中的所有文件
    Files.walk(zipFs.getPath("/"))
        .filter(Files::isRegularFile)
        .forEach(path -> {
            try {
                System.out.println(path + " (" + Files.size(path) + " bytes)");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
}

// ============ 4. 实际应用场景 ============

// 批量操作 JAR 文件
Path jarPath = Path.of("myapp.jar");
try (FileSystem jarFs = FileSystems.newFileSystem(jarPath, (ClassLoader) null)) {
    // 查找所有 .class 文件
    long classCount = Files.walk(jarFs.getPath("/"))
        .filter(p -> p.toString().endsWith(".class"))
        .count();
    System.out.println("JAR 中包含 " + classCount + " 个 class 文件");
}

8. NIO ByteBuffer 增强

8.1 概述

JDK 13 为 ByteBuffer 及相关 Buffer 类新增了多个方法,支持以只读方式访问底层字节数组,以及更灵活地创建 Buffer 视图。

8.2 代码案例

// ============ 1. ByteBuffer 新增方法 ============

// 新增方法:
// ByteBuffer.wrap(byte[] array, int offset, int length) — 已有
// 新增的只读视图方法

// ============ 2. 创建只读 ByteBuffer ============

byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"

// 创建只读包装
ByteBuffer readOnly = ByteBuffer.wrap(data).asReadOnlyBuffer();
// readOnly.put((byte) 0x00); // 抛出 ReadOnlyBufferException

// ============ 3. 切片与视图 ============

ByteBuffer buffer = ByteBuffer.allocate(64);

// 写入数据
buffer.putInt(42);
buffer.putLong(123456789L);
buffer.putDouble(3.14);

// 翻转读取
buffer.flip();

// 读取
int intVal = buffer.getInt();        // 42
long longVal = buffer.getLong();      // 123456789
double doubleVal = buffer.getDouble(); // 3.14

// ============ 4. 实际应用场景:网络协议解析 ============

// 解析自定义二进制协议
byte[] packet = new byte[32];
// ... 从网络接收数据 ...

ByteBuffer buf = ByteBuffer.wrap(packet);
buf.order(ByteOrder.BIG_ENDIAN);

// 解析头部
short version = buf.getShort();   // 2字节版本号
short type = buf.getShort();      // 2字节消息类型
int length = buf.getInt();        // 4字节数据长度
long timestamp = buf.getLong();    // 8字节时间戳

// 解析数据体
byte[] payload = new byte[length];
buf.get(payload);

System.out.printf("版本: %d, 类型: %d, 长度: %d, 时间戳: %d%n",
    version, type, length, timestamp);

9. JVM Constants API 更新

9.1 概述

JDK 13 对 JVM Constants API(JDK 12 引入,JEP 334)进行了小幅更新,增加了新的常量类型描述符,更好地支持类文件中的常量池操作。

9.2 代码案例

// ============ 1. ClassDesc — 类描述符 ============

import java.lang.constant.*;

// 创建类描述符
ClassDesc stringDesc = ClassDesc.of("java.lang.String");
ClassDesc intDesc = ClassDesc.of("int");
ClassDesc listDesc = ClassDesc.of("java.util.List");

// 数组类型
ClassDesc stringArrayDesc = stringDesc.arrayType();     // String[]
ClassDesc intArrayDesc = intDesc.arrayType(2);          // int[][]

System.out.println(stringArrayDesc.descriptorString()); // "[Ljava/lang/String;"
System.out.println(intArrayDesc.descriptorString());     // "[[I"

// ============ 2. MethodTypeDesc — 方法类型描述符 ============

// 描述方法签名
MethodTypeDesc methodDesc = MethodTypeDesc.of(
    ClassDesc.of("void"),                    // 返回类型
    ClassDesc.of("java.lang.String"),        // 参数1
    ClassDesc.of("int")                      // 参数2
);
System.out.println(methodDesc.descriptorString());
// "(Ljava/lang/String;I)V"

// ============ 3. MethodHandleDesc — 方法句柄描述符 ============

// 描述方法句柄(包括调用类型)
MethodHandleDesc handleDesc = MethodHandleDesc.ofMethod(
    DirectMethodHandleDesc.Kind.VIRTUAL,     // 虚方法调用
    ClassDesc.of("java.lang.String"),        // 所属类
    "length",                                 // 方法名
    MethodTypeDesc.of(ClassDesc.of("int"))   // ()I
);
System.out.println(handleDesc);

// ============ 4. DynamicConstantDesc — 动态常量描述符 ============

// 描述 invokedynamic 引导方法
DynamicConstantDesc<String> dcd = DynamicConstantDesc.ofNamed(
    MethodHandleDesc.ofMethod(
        DirectMethodHandleDesc.Kind.STATIC,
        ClassDesc.of("java.lang.runtime.SwitchBootstraps"),
        "typeSwitch",
        MethodTypeDesc.of(
            ClassDesc.of("int"),
            ClassDesc.of("java.lang.invoke.MethodHandles$Lookup"),
            ClassDesc.of("java.lang.String"),
            ClassDesc.ofDescriptor("[Ljava.lang.invoke.TypeDescriptor;"),
            ClassDesc.of("int"),
            ClassDesc.ofDescriptor("[Ljava.lang.Object;")
        )
    ),
    "switch",  // 常量名
    ClassDesc.of("int")  // 类型
);

// ============ 5. 实际应用场景 ============

// 字节码生成工具中使用
class BytecodeGenerator {
    // 生成方法调用的字节码描述
    void generatePrintln() {
        MethodHandleDesc println = MethodHandleDesc.ofMethod(
            DirectMethodHandleDesc.Kind.VIRTUAL,
            ClassDesc.of("java.io.PrintStream"),
            "println",
            MethodTypeDesc.of(
                ClassDesc.of("void"),
                ClassDesc.of("java.lang.String")
            )
        );
        // 可用于 ASM、ByteBuddy 等字节码操作库
    }
}

10. SSL/TLS 增强

10.1 概述

JDK 13 在 SSL/TLS 方面进行了增强,新增了系统属性支持,并改进了 SSLSocket 和 SSLEngine 的行为。

10.2 代码案例

// ============ 1. 新增系统属性 ============

// jdk.tls.keyLimits — 密钥使用量限制
// 格式: <key-type> <max-usage>
// 示例: -Djdk.tls.keyLimits=AES/GCM/NoPadding max:2^37

// 当密钥使用超过限制时,JDK 会自动重新协商
// 防止密码学攻击(如 Sweet32 攻击)

// ============ 2. SSLSocket 增强 ============

import javax.net.ssl.*;
import java.io.*;

// 创建 SSL Socket
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (SSLSocket socket = (SSLSocket) factory.createSocket("example.com", 443)) {
    // 启用特定协议
    socket.setEnabledProtocols(new String[]{"TLSv1.3", "TLSv1.2"});

    // 获取会话信息
    SSLSession session = socket.getSession();
    System.out.println("协议: " + session.getProtocol());
    System.out.println("密码套件: " + session.getCipherSuite());

    // 发送 HTTPS 请求
    OutputStream out = socket.getOutputStream();
    out.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".getBytes());
    out.flush();

    // 读取响应
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// ============ 3. 配置密钥使用限制 ============

// 在代码中设置
System.setProperty("jdk.tls.keyLimits", "AES/GCM/NoPadding max:2^37");

// 或在命令行
// java -Djdk.tls.keyLimits="AES/GCM/NoPadding max:2^37" -jar app.jar

// ============ 4. 实际应用场景 ============

// 高安全要求的服务端配置
SSLContext context = SSLContext.getInstance("TLSv1.3");
context.init(null, null, null);

SSLServerSocketFactory serverFactory = context.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) serverFactory.createServerSocket(8443);

// 仅允许 TLS 1.3
serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"});

// 设置强密码套件
serverSocket.setEnabledCipherSuites(new String[]{
    "TLS_AES_256_GCM_SHA384",
    "TLS_AES_128_GCM_SHA256"
});

11. javax.swing 增强

11.1 概述

JDK 13 对 Swing 进行了小幅增强,主要改进了多显示器支持和 JEditorPane 的 HTML 渲染。

11.2 代码案例

// ============ 1. 多显示器改进 ============

import javax.swing.*;
import java.awt.*;

// JDK 13 改进了多显示器环境下的窗口定位
// GraphicsConfiguration 的行为更加一致

JFrame frame = new JFrame("多显示器测试");
frame.setSize(400, 300);

// 获取所有屏幕
GraphicsDevice[] screens = GraphicsEnvironment.getLocalGraphicsEnvironment()
    .getScreenDevices();

System.out.println("检测到 " + screens.length + " 个显示器");
for (int i = 0; i < screens.length; i++) {
    DisplayMode mode = screens[i].getDisplayMode();
    System.out.printf("屏幕 %d: %dx%d @ %dHz%n",
        i, mode.getWidth(), mode.getHeight(), mode.getRefreshRate());
}

// 在指定屏幕显示
if (screens.length > 1) {
    GraphicsConfiguration gc = screens[1].getDefaultConfiguration();
    frame.setBounds(gc.getBounds().x + 50, gc.getBounds().y + 50, 400, 300);
}

frame.setVisible(true);

// ============ 2. JEditorPane HTML 改进 ============

JEditorPane editorPane = new JEditorPane();
editorPane.setContentType("text/html");
editorPane.setText("""
    <html>
      <body>
        <h2>JDK 13 Swing 增强</h2>
        <p>HTML 渲染得到了改进。</p>
        <ul>
          <li>更好的 CSS 支持</li>
          <li>改进的表格渲染</li>
          <li>更一致的字体处理</li>
        </ul>
      </body>
    </html>
    """);

12. JDK 13 特性总览表

序号特性JEP类型重要性
1文本块 Text Blocks(预览)JEP 355语言★★★★★
2Switch 表达式增强(第二次预览)JEP 354语言★★★★☆
3动态 CDS 归档JEP 350性能★★★★☆
4ZGC 未使用内存回收JEP 351GC★★★★☆
5Socket API 重写JEP 353平台★★★☆☆
6String.formatted() 方法—API★★★★☆
7FileSystems 新工厂方法—API★★☆☆☆
8NIO ByteBuffer 增强—API★★☆☆☆
9JVM Constants API 更新—API★★☆☆☆
10SSL/TLS 密钥使用限制—安全★★★☆☆
11javax.swing 增强—UI★☆☆☆☆

附录:JDK 13 编译与运行注意事项

# 预览特性需要额外参数
# 编译
javac --enable-preview --release 13 MyClass.java

# 运行
java --enable-preview -cp . MyClass

# 非预览特性正常使用
java -jar myapp.jar  # 动态 CDS、ZGC 等不需要 --enable-preview

预览特性状态追踪:

特性JDK 12JDK 13JDK 14JDK 15
Switch 表达式预览第二次预览正式—
Text Blocks—预览第二次预览正式
instanceof 模式匹配——预览第二次预览

总结:JDK 13 是一个承前启后的版本。Text Blocks(预览) 为后续正式版本铺路,yield 关键字 解决了 Switch 表达式的语义问题,动态 CDS 和 ZGC 内存回收 提升了启动性能和资源利用率,Socket API 重写 为网络编程打下更坚实的基础。虽然特性数量不多,但每一项都有明确的价值。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐