JDK12新特性及旧版本变更

新的字符串和文件方法

在 Java 11 中,我们已经获得了一些新的 String 方法,以及Files.readString() 和 writeString() 方法。而在 Java 12,JDK 开发人员对这两个类进一步扩展,新增了一些实用的方法。

String.indent() —— 方便的字符串缩进

在 Java 12 之前,如果我们想给字符串添加缩进,通常需要编写一个辅助方法,在字符串前面插入指定数量的空格。而当需要处理多行文本时,这个方法就会变得相当复杂。

Java 12 直接内置了 String.indent() 方法,使得这一操作变得简单。下面是一个示例,展示如何为多行字符串增加 4 个空格的缩进:

String s = "I am\na multiline\nString.";
System.out.println(s);
System.out.println(s.indent(4));

该程序打印以下内容:

I am
a multiline
String.
    I am
    a multiline
    String.

String.transform() —— 让字符串转换更加灵活

Java 12 引入了 String.transform() 方法,它可以将任意函数应用于字符串,并返回转换后的结果。例如:

String uppercase = "abcde".transform(String::toUpperCase);
Integer i        = "12345".transform(Integer::valueOf);
BigDecimal big   = "1234567891011121314151617181920".transform(BigDecimal::new);

其实,从源码上看,transform() 本质上就是将 Function 作为参数,作用在当前字符串上:

public <R> R transform(Function<? super String, ? extends R> f) {
  return f.apply(this);
}

那么,为什么要使用 transform(),而不是直接写 toUpperCase()new BigDecimal() 呢?

String uppercase = "abcde".toUpperCase();
Integer i        = Integer.valueOf("12345");
BigDecimal big   = new BigDecimal("1234567891011121314151617181920");

transform() 的优势在于,它可以在运行时动态决定要应用的转换逻辑,而上面的传统写法在编译时就已经固定了转换方式。因此,在某些动态场景下,transform() 会更加灵活。

Files.mismatch() —— 快速比较两个文件是否相同

Java 12 还引入了 Files.mismatch() 方法,用于比较两个文件的内容。

  • 如果两个文件完全相同,则返回 -1
  • 如果不同,则返回它们第一个不同字节的位置。
  • 如果其中一个文件在检测到不同之前就已结束,则返回该文件的长度。

这一方法可以用于高效地检查文件差异,避免逐字节比对带来的性能开销。

Teeing Collector —— 让 Stream 同时收集多个结果

在数据处理场景中,我们经常需要对同一组数据执行多个计算,比如同时获取最大值最小值

错误示例:两次终结操作导致异常

假设我们想从一组随机数中计算最大值和最小值,并求它们的差值:(Optional.orElseThrow()Java 10 中引入的方法):

Stream<Integer> numbers = new Random().ints(100).boxed();

int min = numbers.collect(Collectors.minBy(Integer::compareTo)).orElseThrow();
int max = numbers.collect(Collectors.maxBy(Integer::compareTo)).orElseThrow();
long range = (long) max - min;

问题: 运行时会抛出异常:

Exception in thread "main" java.lang.IllegalStateException: 
        stream has already been operated upon or closed
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at eu.happycoders.sandbox.TeeingCollectorTest.main(TeeingCollectorTest.java:12)

原因:
Stream 只能执行一次终结操作,而 collect() 已经终止了 Stream,第二次 collect() 操作会导致 IllegalStateException

传统解决方案:自定义收集器

为了避免多次终结 Stream,我们可以使用自定义收集器,将最大值和最小值同时计算出来:

Stream<Integer> numbers = new Random().ints(100).boxed();

int[] result =
    numbers.collect(
        () -> new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE},
        (minMax, i) -> {
          if (i < minMax[0]) minMax[0] = i;
          if (i > minMax[1]) minMax[1] = i;
        },
        (minMax1, minMax2) -> {
          if (minMax2[0] < minMax1[0]) minMax1[0] = minMax2[0];
          if (minMax2[1] > minMax1[1]) minMax1[1] = minMax2[1];
        });

long range = (long) result[1] - result[0];

缺点:

  • 代码较为复杂,难以阅读。
  • 需要手动处理最小值和最大值的合并逻辑。

Java 12 解决方案:Teeing Collector

Java 12 引入的 Collectors.teeing() 可以让我们在一次 collect 操作中同时计算多个值,然后合并它们的结果:

Stream<Integer> numbers = new Random().ints(100).boxed();

long range =
    numbers.collect(
        Collectors.teeing(
            Collectors.minBy(Integer::compareTo),
            Collectors.maxBy(Integer::compareTo),
            (min, max) -> (long) max.orElseThrow() - min.orElseThrow()));

优势:

  • 更直观、更易读,不需要手动维护数组或合并逻辑。
  • 更符合流式操作的语义,避免多次终结 Stream

Teeing Collector 的工作原理

Collectors.teeing() 需要三个参数:

  1. 第一个收集器(下游收集器) → 计算 minBy(Integer::compareTo)
  2. 第二个收集器(下游收集器) → 计算 maxBy(Integer::compareTo)
  3. 合并函数 → 计算 max - min

它的工作方式类似于“T”字形的数据流分流器,因此得名 Teeing Collector

示意图:

JDK12新特性及旧版本变更

总结

方式 代码复杂度 运行效率 可读性
两次 collect() 简单但会抛异常
自定义收集器 复杂
Teeing Collector 简单

Collectors.teeing() 让我们可以一次性对 Stream 进行多个计算,并合并结果,是 Java 12 处理数据流的强大工具。

支持紧凑数字格式

Java 通过 NumberFormat.getCompactNumberInstance() 方法支持了“紧凑数字格式”,该格式有助于将数字表示为易于人类理解的简洁形式,比如“2M”表示 200 万,或“30B”表示 30 亿。

紧凑数字格式的使用

使用 NumberFormat.getCompactNumberInstance(),我们可以为不同区域(如美国地区)设置短格式和长格式。下面是一个简单的示例,展示如何通过短格式和长格式分别显示数字:

NumberFormat nfShort =
    NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
NumberFormat nfLong =
    NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);

System.out.println("        1,000 short -> " + nfShort.format(1_000));
System.out.println("      456,789 short -> " + nfShort.format(456_789));
System.out.println("    2,000,000 short -> " + nfShort.format(2_000_000));
System.out.println("3,456,789,000 short -> " + nfShort.format(3_456_789_000L));
System.out.println();
System.out.println("        1,000 long -> " + nfLong.format(1_000));
System.out.println("      456,789 long -> " + nfLong.format(456_789));
System.out.println("    2,000,000 long -> " + nfLong.format(2_000_000));
System.out.println("3,456,789,000 long -> " + nfLong.format(3_456_789_000L));

输出结果

该程序的输出将显示如下内容:

        1,000 short -> 1K
      456,789 short -> 457K
    2,000,000 short -> 2M
3,456,789,000 short -> 3B

        1,000 long -> 1 thousand
      456,789 long -> 457 thousand
    2,000,000 long -> 2 million
3,456,789,000 long -> 3 billion

短格式与长格式

  • 短格式 (SHORT):数字会被简化为如 K(千)、M(百万)、B(十亿)等。例如,1000 会显示为 "1K"。
  • 长格式 (LONG):数字以更为详细的方式展示。例如,1000 会显示为 "1 thousand"。

“紧凑数字格式”在相应的Unicode标准中有定义。

(针对“紧凑数字格式”的 JDK 增强提案不存在。)

性能改进

为了确保 Java 应用程序启动更快、垃圾收集器延迟更低、内存占用更少,以下是一些 JDK 中的性能改进:

默认 CDS 档案

在 Java 10 的文章中,你可以找到对类数据共享(CDS)的介绍。

在早期版本中,您需要使用命令 java -Xshare:dump 为每个 Java 安装生成 classes.jsa 的共享存档文件。而随着 JDK 增强提案 341 的发布,JDK 所有 64 位版本现在默认包含这个共享存档文件。也就是说,不再需要执行 java -Xshare:dump,Java 应用程序会默认使用这个 CDS 存档,从而提升了启动性能。

G1 的可中止混合收集

G1 垃圾收集器的设计目标之一是尽量减少暂停时间。通过 -XX:MaxGCPauseMillis 参数,您可以指定最大暂停时间。如果没有设置该参数,默认的最大暂停时间为 200 毫秒。

G1 使用启发式方法来选择哪些堆区域需要进行垃圾收集。在“混合收集”模式下(即同时清理年轻代和老年代),可能会因为启发式方法选择了过大的收集集,而导致应用程序暂停时间超出了预期。

为了解决这个问题, JDK 增强提案 344 优化了混合集合。在暂停时间频繁超过最大值时,收集集被分为强制部分和可选部分。强制部分会不中断地执行,而可选部分则会分步执行,直到达到最大暂停时间。这个算法还调整了启发式方法,以便在以后能够更快地确定适合在最大暂停时间内完成的收集集合。

及时从 G1 归还未使用的已提交内存

在许多收费的内存环境中,垃圾收集器应能够及时将未使用的内存返回给操作系统。然而,G1 垃圾收集器通常只会在垃圾收集周期中释放内存。若堆分配或对象分配率未触发垃圾收集周期,则不会释放任何内存。

考虑以下情况:如果应用程序只进行每天一次的内存密集型批处理,其余时间几乎空闲,批处理结束后没有触发垃圾收集。此时,应用程序仍然占用未被垃圾回收的内存,但没有释放给操作系统,从而可能导致浪费资源。

JDK12新特性及旧版本变更

为了解决这一问题,JEP 346 引入了定期的垃圾收集周期,尤其是在应用程序空闲时。这使得 G1 在不活跃时也能够定期启动垃圾收集,快速释放不再使用的内存。默认情况下,这个功能是禁用的。如果希望启用它,可以通过 -XX:G1PeriodicGCInterval 参数设置检查的时间间隔。这样,内存可以更快地被释放:

JDK12新特性及旧版本变更

实验性和预览性功能

Switch 表达式(预览)

得益于JDK 增强提案 325,Java 现在支持通过逗号分隔多个 case 并使用箭头符号简化 switch 语句,减少了容易出错的 break 语句。以下是新的语法示例:

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

此外,Java 12 引入了 switch 表达式,允许将不同的 case 结果赋值给变量:

int numLetters = switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> 6;
  case TUESDAY                -> 7;
  case THURSDAY, SATURDAY     -> 8;
  case WEDNESDAY              -> 9;
};

switch 表达式 也支持传统语法(带冒号和 break):

int numLetters = switch (day) {
  case MONDAY, FRIDAY, SUNDAY:
    break 6;
  case TUESDAY:
    break 7;
  case THURSDAY, SATURDAY:
    break 8;
  case WEDNESDAY:
    break 9;
};

(注:在以下预览中将break被替换为)。yield

要在 Java 12 中启用 Switch 表达式,您需要在 IDE 或通过命令行启用预览功能。例如,在 IntelliJ 中可以通过文件→项目结构→项目设置→项目→项目语言级别来启用,或者在运行时使用 --enable-preview 参数。

Shenandoah:低暂停时间垃圾收集器(实验性)

Java 11 中,Oracle 的“Z 垃圾收集器”作为一项实验性功能被引入。

Java 12 带来了另一个低延迟垃圾收集器:由 Red Hat 开发的“Shenandoah”。与 ZGC 一样,Shenandoah 旨在最大限度地减少完整 GC 的暂停时间。

您可以在 java 命令行中使用以下选项启用 Shenandoah:

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

(该实验版本在JDK 增强提案 189中定义。)

Java 12 中的其他变化(无需每位 Java 开发者都关注)

以下列出了 Java 12 中的一些变化,虽然它们不会对大多数 Java 开发者的日常工作产生重大影响,但了解这些变化也是有益的。

Unicode 11

在Java 11 增加了对 Unicode 10 的支持之后,Java 12 中对 Unicode 11 的支持也得到了提升。这意味着,特别是类StringCharacter必须处理 Unicode 11 中添加的新字符、代码块和脚本。

有关示例,请参阅前面链接的有关 Unicode 10 的部分。

(没有针对 Unicode 11 支持的 JDK 增强提案。)

微基准测试套件

到目前为止,JDK 类库的微基准测试一直作为单独的项目进行管理。这些基准测试定期测量 JDK 类库的性能,例如用于确保 JDK 方法不会随着新 Java 版本的发布而变慢。

JDK 增强提案 230将现有的微基准测试集合移入 JDK 源代码,以简化测试的执行和发展。

JVM 常量 API

JVM 的常量池包含在编译 .java 文件时产生的常量,这些常量包括 Java 代码中定义的常量(例如 "Hello world!"),以及引用的类和方法名称(例如 "java/lang/System", "out", "println")。这些常量通过 .class 文件的字节码进行引用。

JDK 增强提案 334旨在通过提供新的接口和类,使得编写读取或写入 JVM 字节码的 Java 程序变得更加便捷。这些新接口和类位于 java.lang.constant 包中,提供了新的 ConstantDesc 接口等。

一个 AArch64 端口,而不是两个

Java 12 移除了针对 64 位 ARM 架构的两个不同端口,集中开发资源,专注于单一的 aarch64 端口。此变化基于JDK 增强提案 340

Java 12 中所有变更的完整列表

有关更改的完整列表,请参阅 官方 Java 12 发行说明

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
javaJDKjvm

java-11-features

2025-3-21 17:38:33

javaJDKjvm

java.sleep()

2025-3-31 8:27:33

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧