跳到主要内容

JEP 223:新版本字符串方案

QWen Max 中英对照

概述

定义一个版本字符串方案,可以轻松区分主要版本、次要版本和安全更新版本,并将其应用于 JDK。

目标

  • 易于人类理解,也易于程序解析。

  • 与当前的行业实践保持一致,特别是 语义化版本控制

  • 可被现有的打包系统和平台部署机制采用,包括 RPMdpkgIPSJava 网络启动协议 (JNLP)

  • 消除当前在版本字符串的一个元素中编码两种信息的做法,即次要版本号和安全级别,这种做法难以解读并且导致跳过许多版本号。

  • 提供一个简单的 API 用于版本字符串的解析、验证和比较。

非目标

  • 更改此 JEP 目标发布之前任何发布所使用的版本字符串格式。

动机

哪个版本包含所有最新的安全修复:JDK 7 Update 55,还是 JDK 7 Update 60?

看起来 JDK 7 Update 60 比 Update 55 多了五个版本,因此它肯定包含更多的安全修复,对吧?

遗憾的是,这个结论是错误的:这两个版本都包含完全相同的安全修复程序。要理解这个答案,您首先需要了解 JDK 更新版本的当前编号方案。包含安全修复之外更改的小版本号是 20 的倍数。基于前一个小版本的安全修复版本号则是奇数,并且每次递增 5(如有必要则递增 6,以确保更新版本号保持为奇数)。要判断某个小版本是否比之前的版本更安全,最终需要查看发行说明或源代码。

名为“JDK 7 Update 60”、“1.7.0_60”和“JDK 7u60”的版本之间有什么区别?

这些只是同一个发行版本的不同名称。这些差异使得识别和验证等效的发行版本变得困难。简单地对解析后的标记序列进行逐点比较是不够的,相反,需要一个相当复杂的算法。使用小写的“u”不是行业标准,也不是语言中立的。

是时候采用一个更简单、更直观的版本控制方案了。

描述

版本号

一个版本号$VNUM,是由句点字符(U+002E)分隔的非空元素序列。元素要么是零,要么是不带前导零的无符号整数数字。版本号中的最后一个元素不能为零。格式如下:

[1-9][0-9]*((\.0)*\.[1-9][0-9]*)*

该序列可以是任意长度,但前三个元素被赋予了特定的含义,具体如下:

$MAJOR.$MINOR.$SECURITY
  • $MAJOR --- 主版本号,当发布包含重大新功能的主要版本时递增,这些新功能在新版 Java SE 平台规范中指定,例如,JSR 337 对应 Java SE 8。在至少提前一个主版本通知的情况下,主要版本中可能会移除某些功能,并且在合理情况下可能会引入不兼容的变更。JDK 8 的 $MAJOR 版本号为 8;JDK 9 的 $MAJOR 版本号为 9。当 $MAJOR 递增时,所有后续元素都会被移除。

  • $MINOR --- 次版本号,当发布次要更新版本时递增,该版本可能包含兼容性修复、相关平台规范的维护版本要求的标准 API 修订,以及超出该规范范围的实现功能,例如新的 JDK 特定 API、额外的服务提供者、新的垃圾收集器,以及针对新硬件架构的移植。

  • $SECURITY --- 安全级别,当发布包含关键修复的安全更新版本时递增,这些修复包括提升安全性的必要补丁。当 $MINOR 递增时,$SECURITY 不会重置为零。因此,对于给定的 $MAJOR 值,$SECURITY 值越高,始终表示更安全的版本,无论 $MINOR 的值如何。

版本号的第四位及后续位数可由 JDK 代码库的下游使用者自由使用。例如,使用者可以利用第四位来标识补丁发布,其中除了对应安全发布中的安全修复之外,还包含少量关键的非安全修复。

版本号不包括尾随的零元素;,如果 $SECURITY 的值为零,则省略 $SECURITY,如果 $MINOR$SECURITY 的值都为零,则省略 $MINOR

版本号中的数字序列会以数值、逐点的方式与另一个这样的序列进行比较;例如9.9.1 小于 9.10.3。如果一个序列比另一个短,则较短序列中缺失的元素被认为小于较长序列中的对应元素;例如9.1.2 小于 9.1.2.1

版本字符串

一个版本字符串$VSTR,由如上所述的版本号 $VNUM 组成,后面可选地跟随预发布和构建信息,采用以下格式之一:

$VNUM(-$PRE)?\+$BUILD(-$OPT)?
$VNUM-$PRE(-$OPT)?
$VNUM(+-$OPT)?

哪里:

  • $PRE,匹配 ([a-zA-Z0-9]+) --- 预发布标识符。通常是 ea,用于正在积极开发中且可能不稳定的早期访问版本,或者是 internal,用于内部开发者构建。

    在比较两个版本字符串时,带有预发布标识符的字符串总是小于具有相同 $VNUM 但没有此类标识符的字符串。当预发布标识符仅由数字组成时按数值比较,否则按字典顺序比较。纯数字标识符被认为小于非数字标识符。

  • $BUILD,匹配 (0|[1-9][0-9]*) --- 构建编号,每次提升构建时递增。当 $VNUM 的任何部分递增时,$BUILD 将重置为一。

    在比较两个具有相同 $VNUM$PRE 组件的版本字符串时,缺少 $BUILD 组件的字符串总是小于具有 $BUILD 组件的字符串;否则,$BUILD 编号将按数值进行比较。

  • $OPT,匹配 ([-a-zA-Z0-9\.]+) --- 如果需要,附加的构建信息。对于 internal 构建,通常会包含构建的日期和时间。

    在比较两个版本字符串时,$OPT 的值(如果存在)是否显著取决于所选择的比较方法。

版本号 10-ea 匹配 $VNUM = "10"$PRE = "ea"。版本号 10+-ea 匹配 $VNUM = "10"$OPT = "ea"

下表列出了使用现有格式和建议格式的 JDK 9 可能的版本字符串,以作比较:

Existing                Proposed
Release Type long short long short
------------ -------------------- --------------------
Early Access 1.9.0-ea-b19 9-ea 9-ea+19 9-ea
Major 1.9.0-b100 9 9+100 9
Security #1 1.9.0_5-b20 9u5 9.0.1+20 9.0.1
Security #2 1.9.0_11-b12 9u11 9.0.2+12 9.0.2
Minor #1 1.9.0_20-b62 9u20 9.1.2+62 9.1.2
Security #3 1.9.0_25-b15 9u25 9.1.3+15 9.1.3
Security #4 1.9.0_31-b08 9u31 9.1.4+8 9.1.4
Minor #2 1.9.0_40-b45 9u40 9.2.4+45 9.2.4

作为参考,此表显示了新格式的版本字符串,假设它们是用于某些 JDK 7 更新和安全发布的:

Actual               Hypothetical
Release Type long short long short
------------ -------------------- -------------------
Security 2013/04 1.7.0_21-b11 7u21 7.4.10+11 7.4.10
Security 2013/06 1.7.0_25-b15 7u25 7.4.11+15 7.4.11
Minor 2013/09 1.7.0_40-b43 7u40 7.5.11+43 7.5.11
Security 2013/10 1.7.0_45-b18 7u45 7.5.12+18 7.5.12
Security 2014/01 1.7.0_51-b13 7u51 7.5.13+13 7.5.13
Security 2014/04 1.7.0_55-b13 7u55 7.5.14+13 7.5.14
Minor 2014/05 1.7.0_60-b19 7u60 7.6.14+19 7.6.14
Security 2014/07 1.7.0_65-b20 7u65 7.6.15+20 7.6.15

从版本号中删除初始的 1 元素

该提案删除了 JDK 版本号中的初始 1 元素。也就是说,它建议 JDK 9 的第一个发行版的版本号为 9.0.0,而不是 1.9.0.0

差不多二十年了,很明显,当前版本号方案的第二部分是 JDK 的实际上的 $MAJOR 版本号。当我们添加重要的新功能时,以及当我们进行不兼容的更改时,我们会增加该部分的数字。

我们可以开始将当前方案的初始元素视为 $MAJOR 版本号,但这样 JDK 9 的版本号就会是 2.0.0,尽管大家已经习惯称其为 “JDK 9”。这样做对任何人都没有帮助。

如果保留最初的 1,那么 JDK 的版本号将继续违反语义化版本控制的原则,而新接触 Java 的开发者也会继续对 例如 1.99 之间的差异感到困惑。

丢弃初始的 1 存在一定风险。比较版本号的方法有很多;有些方法可以正确工作,而有些则不行。

  • 通过解析版本号的各个元素并进行数值比较的现有代码将继续正常工作,因为九大于一;即,9.0.0 将被认为晚于 1.8.0

  • 当初始元素值为 1 时跳过该元素的现有代码也将继续正常工作,因为在新方案中,初始元素永远不会具有该值。

  • 然而,假设初始元素值为 1 的现有代码在比较版本号时总是跳到第二个元素,则无法正确工作;例如,此类代码会认为 9.0.1 早于 1.8.0

轶证据表明,第三类中的现有代码并不常见,但我们也欢迎有数据来反驳这一观点。

API

将定义一个用于解析、验证和比较版本字符串的简单 Java API(80723798144062):

package java.lang;

import java.util.Optional;

public class Runtime {

public static Version version();

public static class Version
implements Comparable<Version>
{

public static Version parse(String);

public int major();
public int minor();
public int security();

public List<Integer> version();
public Optional<String> pre();
public Optional<Integer> build();
public Optional<String> optional();

public int compareTo(Version o);
public int compareToIgnoreOpt(Version o);

public boolean equals(Object o);
public boolean equalsIgnoreOpt(Object o);

public String toString();
public int hashCode();
}
}

将会定义一个等效的 C API,最有可能是以修订版的 jvm_version_info 结构体 的形式。

JDK 中所有检查和比较 JDK 版本字符串的代码都将更新为使用这些 API。鼓励其库或应用程序检查和比较 JDK 版本字符串的开发人员使用这些 API。

系统属性

以下系统属性返回的值由此 JEP 修改。其通用语法如下:

Name                            Syntax
------------------------------ --------------
java.version $VNUM(\-$PRE)?
java.runtime.version $VSTR
java.vm.version $VSTR
java.specification.version $VNUM
java.vm.specification.version $VNUM

系统属性 java.class.version 不受影响。

下表显示了不同发布类型现有的和建议的值:

发布类型现有值建议值
Alpha10001500
Beta20002500
Release Candidate30003500
Stable Release40004500
System Property                   Existing      Proposed
------------------------------- ------------ --------
Early Access
java.version 1.9.0-ea 9-ea
java.runtime.version 1.9.0-ea-b73 9-ea+73
java.vm.version 1.9.0-ea-b73 9-ea+73
java.specification.version 1.9 9
java.vm.specification.version 1.9 9

Major (GA)
java.version 1.9.0 9
java.runtime.version 1.9.0-b100 9+100
java.vm.version 1.9.0-b100 9+100
java.specification.version 1.9 9
java.vm.specification.version 1.9 9

Minor #1 (GA)
java.version 1.9.0_20 9.1.2
java.runtime.version 1.9.0_20-b62 9.1.2+62
java.vm.version 1.9.0_20-b62 9.1.2+62
java.specification.version 1.9 9
java.vm.specification.version 1.9 9

Security #1 (GA)
java.version 1.9.0_5 9.0.1
java.runtime.version 1.9.0_5-b20 9.0.1+20
java.vm.version 1.9.0_5-b20 9.0.1+20
java.specification.version 1.9 9
java.vm.specification.version 1.9 9

请注意,所有历史上通过这些系统属性中的 . 来识别版本的代码都需要进行检查,并可能需要修改。例如,对于主要版本发布,System.getProperty("java.version").indexof('.') 将返回 -1

启动器

在 OpenJDK 的 java 启动器实现中,系统属性被用于报告版本信息时使用,例如 java -versionjava -fullversionjava -showversion

启动器输出继续依赖于系统属性,具体如下:

$ java -version
openjdk version \"${java.version}\"
${java.runtime.name} (build ${java.runtime.version})
${java.vm.name} (build ${java.vm.version}, ${java.vm.info})

$ java -showversion < ... >
openjdk version \"${java.version}\"
${java.runtime.name} (build ${java.runtime.version})
${java.vm.name} (build ${java.vm.version}, ${java.vm.info})
[ ... ]

$ java -fullversion
openjdk full version \"${java.runtime.version}\"

实现细节可以在 源码 中找到。

@since JavaDoc 标签

@since JavaDoc 标签的值将继续与系统属性 java.specification.version 保持一致;因此,新的 JDK 9 API 将由 @since 9 表示。

Mercurial 变更集标签

Mercurial 标签用于标识推广的变更集。诸如 Code Tool'sjcheck 这类工具(用于验证推送到 JDK 发布森林的所有变更集)将得到增强,以支持使用新版本方案的标签。

Mercurial 标签的一般语法为 jdk\-$VNUM\+$BUILD。下表显示了不同发行类型建议使用的值:

发行类型建议值
Release Type      Proposed
---------------- -----------
Major (GA) jdk-9+100
Minor #1 (GA) jdk-9.1.2+27
Security #1 (GA) jdk-9.0.1+3

一些工具可能需要支持现有的和建议的标签格式。

测试

更改版本字符串的语法和语义需要对所有组件区域进行广泛的测试。不依赖于 JDK 版本字符串的现有测试应继续通过。