1. 概述

在 DevOps 流行的今天,一天内多次构建和部署应用已是常态。✅

为了区分不同构建产物,每次构建都会被打上唯一的版本号。而有时我们还需要在程序中对这些版本号字符串进行比较,比如判断是否需要升级、做灰度发布控制等。

本文将介绍几种在 Java 中比较版本号的常用方式,涵盖多个流行库的实践方案。最后还会手撸一个通用的自定义实现,方便你在标准库不满足需求时快速扩展。

⚠️ 注意:本文面向有经验的开发者,基础概念不再赘述。

2. 使用 maven-artifact

Maven 作为老牌构建工具,其版本管理能力非常成熟。我们可以直接借用 maven-artifact 模块中的版本比较逻辑。

2.1 Maven 依赖

引入最新版依赖(截至写作时为 3.6.3):

<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-artifact</artifactId>
    <version>3.6.3</version>
</dependency>

2.2 ComparableVersion 类

核心类是 ComparableVersion,它支持无限层级的版本组件比较,且实现了 Comparable 接口,可以直接用于排序。

示例代码如下:

ComparableVersion version1_1 = new ComparableVersion("1.1");
ComparableVersion version1_2 = new ComparableVersion("1.2");
ComparableVersion version1_3 = new ComparableVersion("1.3");

assertTrue(version1_1.compareTo(version1_2) < 0);
assertTrue(version1_3.compareTo(version1_2) > 0);

相同版本返回 0:

ComparableVersion version1_1_0 = new ComparableVersion("1.1.0");
assertEquals(0, version1_1.compareTo(version1_1_0));

优势:自动忽略末尾的 .0,语义化处理更贴近实际使用场景。

2.3 分隔符与修饰符(Qualifiers)

ComparableVersion 支持 .- 作为分隔符:

  • . 用于划分主次版本
  • - 后接修饰符(qualifier),表示预发布版本

支持的常见 qualifier(按优先级升序排列):

  • alpha < beta < milestone < rc < snapshot < 正式版

示例验证:

ComparableVersion version1_1_alpha = new ComparableVersion("1.1-alpha");
assertTrue(version1_1.compareTo(version1_1_alpha) > 0); // 1.1 > 1.1-alpha

多个 qualifier 的比较:

ComparableVersion version1_1_beta = new ComparableVersion("1.1-beta");
ComparableVersion version1_1_milestone = new ComparableVersion("1.1-milestone");
ComparableVersion version1_1_rc = new ComparableVersion("1.1-rc");
ComparableVersion version1_1_snapshot = new ComparableVersion("1.1-snapshot");

assertTrue(version1_1_alpha.compareTo(version1_1_beta) < 0);
assertTrue(version1_1_beta.compareTo(version1_1_milestone) < 0);
assertTrue(version1_1_rc.compareTo(version1_1_snapshot) < 0);
assertTrue(version1_1_snapshot.compareTo(version1_1) < 0);

冷知识:你甚至可以自定义 qualifier,比如 -exp-test,它们会按字典序排在已知 qualifier 之后,且不区分大小写。

ComparableVersion version1_1_c = new ComparableVersion("1.1-c");
ComparableVersion version1_1_z = new ComparableVersion("1.1-z");
ComparableVersion version1_1_1 = new ComparableVersion("1.1.1");

assertTrue(version1_1_c.compareTo(version1_1_z) < 0);
assertTrue(version1_1_z.compareTo(version1_1_1) < 0); // 自定义 qualifier < 数字版本

3. 使用 gradle-core

Gradle 也有自己的版本比较机制,位于 gradle-core 模块中。

3.1 Maven 依赖

<dependency>
    <groupId>org.gradle</groupId>
    <artifactId>gradle-core</artifactId>
    <version>6.1.1</version>
</dependency>

⚠️ 注意:该包体积较大,仅为了版本比较而引入可能得不偿失,建议仅在已有 Gradle 集成的项目中使用。

3.2 VersionNumber 类

核心类是 VersionNumber,用法类似 Maven 的 ComparableVersion

VersionNumber version1_1 = VersionNumber.parse("1.1");
VersionNumber version1_2 = VersionNumber.parse("1.2");
VersionNumber version1_3 = VersionNumber.parse("1.3");

assertTrue(version1_1.compareTo(version1_2) < 0);
assertTrue(version1_3.compareTo(version1_2) > 0);

VersionNumber version1_1_0 = VersionNumber.parse("1.1.0");
assertEquals(0, version1_1.compareTo(version1_1_0));

3.3 版本组件限制

VersionNumber 只支持最多五层结构:

  • Major
  • Minor
  • Micro
  • Patch
  • Qualifier

示例:

VersionNumber version1_1_1_1_alpha = VersionNumber.parse("1.1.1.1-alpha"); 
assertTrue(version1_1.compareTo(version1_1_1_1_alpha) < 0); 

VersionNumber version1_1_beta = VersionNumber.parse("1.1.0.0-beta"); 
assertTrue(version1_1_beta.compareTo(version1_1_1_1_alpha) < 0);

3.4 支持的版本格式

支持两种常见格式:

  • Major.Minor.Micro-Qualifier
  • Major.Minor.Micro.Patch-Qualifier
VersionNumber version1_1_1_snapshot = VersionNumber.parse("1.1.1-snapshot");
assertTrue(version1_1_1_1_alpha.compareTo(version1_1_1_snapshot) < 0);

局限:灵活性不如 Maven,不支持任意深度的版本号。

4. 使用 jackson-core

Jackson 不只是 JSON 处理工具,它的 jackson-core 也提供了版本管理功能。

4.1 Maven 依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.16.0</version>
</dependency>

4.2 Version 类

Version 类不仅能存版本号,还能带上 groupIdartifactId,适合做组件版本管理。

构造函数签名:

public Version(int major, int minor, int patchLevel, String snapshotInfo, String groupId, String artifactId)

使用示例:

Version version1_1 = new Version(1, 1, 0, null, null, null);
Version version1_2 = new Version(1, 2, 0, null, null, null);
Version version1_3 = new Version(1, 3, 0, null, null, null);

assertTrue(version1_1.compareTo(version1_2) < 0);
assertTrue(version1_3.compareTo(version1_2) > 0);

Version version1_1_1 = new Version(1, 1, 1, null, null, null);
assertTrue(version1_1.compareTo(version1_1_1) < 0);

4.3 snapshotInfo 字段

用于标识快照版本:

Version version1_1_snapshot = new Version(1, 1, 0, "snapshot", null, null); 
assertEquals(1, version1_1.compareTo(version1_1_snapshot)); // 正式版 > 快照版

判断是否为快照:

assertTrue(version1_1_snapshot.isSnapshot());

4.4 groupId 与 artifactId 比较

比较时还会按字典序比较 groupIdartifactId

Version version1_1_maven = new Version(1, 1, 0, null, "org.apache.maven", null);
Version version1_1_gradle = new Version(1, 1, 0, null, "org.gradle", null);
assertTrue(version1_1_maven.compareTo(version1_1_gradle) < 0);

适用场景:适合在插件系统或模块化架构中做精确版本匹配。

5. 使用 Semver4J

如果你遵循 语义化版本规范(SemVer),那 semver4j 是个不错的选择。

5.1 Maven 依赖

<dependency>
    <groupId>com.vdurmont</groupId>
    <artifactId>semver4j</artifactId>
    <version>3.1.0</version>
</dependency>

5.2 Semver 类

创建版本对象:

Semver version1_1 = new Semver("1.1.0");
Semver version1_2 = new Semver("1.2.0");
Semver version1_3 = new Semver("1.3.0");

assertTrue(version1_1.compareTo(version1_2) < 0);
assertTrue(version1_3.compareTo(version1_2) > 0);

内部会解析出 majorminorpatch 三个部分。

5.3 内置比较方法

提供语义清晰的判断方法:

Semver version1_1_alpha = new Semver("1.1.0-alpha"); 
assertTrue(version1_1.isGreaterThan(version1_1_alpha)); 

Semver version1_1_beta = new Semver("1.1.0-beta"); 
assertTrue(version1_1_alpha.isLowerThan(version1_1_beta)); 

assertTrue(version1_1.isEqualTo("1.1.0"));

还能获取两个版本的主要差异层级:

assertEquals(VersionDiff.MAJOR, version1_1.diff("2.1.0"));
assertEquals(VersionDiff.MINOR, version1_1.diff("1.2.3"));
assertEquals(VersionDiff.PATCH, version1_1.diff("1.1.1"));

5.4 稳定性判断

通过 isStable() 判断是否为稳定版:

assertTrue(version1_1.isStable());
assertFalse(version1_1_alpha.isStable());

优点:API 设计直观,符合 SemVer 规范,适合标准化项目。

6. 自定义解决方案

如果上述库都不满足你的特殊需求(比如自定义分隔符、特殊修饰符),那就得自己写了。

下面是一个简单粗暴但实用的实现,适用于纯数字点分版本:

public static int compareVersions(String version1, String version2) {
    int comparisonResult = 0;
    
    String[] version1Splits = version1.split("\\.");
    String[] version2Splits = version2.split("\\.");
    int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length);

    for (int i = 0; i < maxLengthOfVersionSplits; i++){
        Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0;
        Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0;
        int compare = v1.compareTo(v2);
        if (compare != 0) {
            comparisonResult = compare;
            break;
        }
    }
    return comparisonResult;
}

测试用例:

assertTrue(VersionCompare.compareVersions("1.0.1", "1.1.2") < 0);
assertTrue(VersionCompare.compareVersions("1.0.1", "1.10") < 0);
assertTrue(VersionCompare.compareVersions("1.1.2", "1.0.1") > 0);
assertTrue(VersionCompare.compareVersions("1.1.2", "1.2.0") < 0);
assertEquals(0, VersionCompare.compareVersions("1.3.0", "1.3"));

⚠️ 局限性

  • 仅支持纯数字
  • 不支持 -alpha 这类修饰符

扩展思路

若需支持字母,可用正则拆分:

// 示例:拆分 "1.2.3-beta" -> ["1","2","3","beta"]
String[] parts = version.split("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)|\\.");

然后对每段做类型判断:数字转 Integer,字符串按字典序比较。

7. 总结

方案 适用场景 推荐指数
maven-artifact 通用性强,支持复杂 qualifier ⭐⭐⭐⭐⭐
gradle-core Gradle 生态内使用 ⭐⭐⭐
jackson-core 组件级版本管理 ⭐⭐⭐⭐
semver4j 严格遵循 SemVer ⭐⭐⭐⭐⭐
自定义 特殊格式或轻量需求 ⭐⭐⭐⭐

建议

  • 大多数情况优先选 maven-artifactsemver4j
  • 已有依赖的项目可复用对应库
  • 性能敏感场景可考虑轻量自定义实现

所有示例代码已上传至 GitHub:https://github.com/yourname/java-version-compare-demo


原始标题:Version Comparison in Java