Version Comparison in Java – Java中的版本比较

最后修改: 2020年 7月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

With the advancement of DevOps technologies, it’s common to build and deploy an application multiple times in a day.

随着DevOps技术的发展,在一天内多次构建和部署一个应用程序是很常见的。

Therefore, every build is assigned a unique version number so we can distinguish between builds. Sometimes, a need arises to compare the version strings programmatically.

因此,每个构建都被分配了一个唯一的版本号,这样我们就可以区分不同的构建了。有时,需要以编程方式比较版本字符串。

In this article, we’ll explore a few ways to compare version strings in Java through various libraries. At last, we’ll write a custom program to handle generic version-string comparison.

在这篇文章中,我们将通过各种库来探索在Java中比较版本字符串的几种方法。最后,我们将编写一个自定义程序来处理通用的版本字符串比较。

2. Using maven-artifact

2.使用maven-artifact

To start with, let’s explore how Maven handles version comparison.

首先,我们来探讨一下Maven如何处理版本比较。

2.1. Maven Dependency

2.1.Maven的依赖性

First, we’ll add the latest maven-artifact Maven dependency to our pom.xml:

首先,我们要把最新的maven-artifact Maven依赖性添加到我们的pom.xml

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

2.2. ComparableVersion

2.2.ComparableVersion

Let’s explore the ComparableVersion class. It provides a generic implementation of version comparison with an unlimited number of version components.

让我们探讨一下ComparableVersion类。它提供了一个版本比较的通用实现,具有无限数量的版本组件

It contains a compareTo method, and the result of the comparison will be greater than or less than 0 when one version is greater than or less than the other, respectively:

它包含一个compareTo方法,当一个版本大于或小于另一个版本时,比较的结果将分别大于或小于0。

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);

Here, we can confirm that the 1.1 version is less than the 1.2 version, and the 1.3 version is greater than the 1.2 version.

在这里,我们可以确认1.1版本小于1.2版本,而1.3版本大于1.2版本。

However, we will get 0 as a result when comparing the same versions:

然而,在比较相同的版本时,我们会得到0的结果。

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

2.3. Version Separators and Qualifiers

2.3.版本分隔符和限定符

Additionally, the ComparableVersion class respects the dot (.) and hyphen (-) as separators, where the dot separates major and minor versions, and the hyphen defines qualifiers:

此外,ComparableVersion类尊重点(.)和连字符(-)作为分隔符,其中点分隔了主要和次要版本,连字符定义了限定符

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

Here, we can confirm that the 1.1 version is greater than the 1.1-alpha version.

在这里,我们可以确认,1.1版本大于1.1-alpha版本。

There are a few well-known qualifiers supported by the ComparableVersion like the alpha, beta, milestone, RC, and snapshot (in the order of lowest to highest):

ComparableVersion支持几个著名的限定词,如alphabetamilestoneRCsnapshot(按照从低到高的顺序)。

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);

Also, it allows us to define unknown qualifiers and respects their order, after the already discussed known qualifiers, with case-insensitive lexical order:

此外,它还允许我们定义未知的限定词,并尊重它们的顺序,在已经讨论过的已知限定词之后,采用不区分大小写的词汇顺序。

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);

3. Using gradle-core

3.使用gradle-core

Like Maven, Gradle also has the built-in capability to handle version comparison.

与Maven一样,Gradle也具有处理版本比较的内置功能。

3.1. Maven Dependency

3.1.Maven的依赖性

First, let’s add the latest gradle-core Maven dependency from the Gradle Releases repo:

首先,让我们从Gradle Releases repo中添加最新的gradle-coreMaven依赖。

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

3.2. VersionNumber

3.2.版本号

The VersionNumber class provided by Gradle compares two versions, similar to Maven’s ComparableVersion class:

Gradle提供的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. Version Components

3.3 版本组件

Unlike the ComparableVersion class, the VersionNumber class supports only five version components — Major, Minor, Micro, Patch, and Qualifier:

ComparableVersion类不同,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. Version Schemes

3.4 版本计划

Also, VersionNumber supports a couple of different version schemes like Major.Minor.Micro-Qualifier and Major.Minor.Micro.Patch-Qualifier:

此外,VersionNumber支持几个不同的版本方案,如Major.Minor.Micro-QualifierMajor.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);

4. Using jackson-core

4.使用jackson-core

4.1. Maven Dependency

4.1.Maven的依赖性

Similar to other dependencies, let’s add the latest jackson-core Maven dependency to our pom.xml:

与其他依赖关系类似,让我们把最新的jackson-core Maven依赖关系添加到我们的pom.xml

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

4.2. Version

4.2.版本

Then, we can examine Jackson‘s Version class, which can hold versioning information of a component along with the optional groupId and artifactId values.

然后,我们可以检查JacksonVersion类,它可以与可选的groupIdartifactId值一起保存一个组件的版本信息

Therefore, the constructor of the Version class allows us to define groupId and artifactId, along with components like Major, Minor, and Patch:

因此,Version类的构造函数允许我们定义groupIdartifactId,以及MajorMinorPatch等组件。

public Version (int major, int minor, int patchLevel, String snapshotInfo, String groupId, String artifactId) {
    //...
}

So, let’s compare a few versions using the Version class:

因此,让我们使用Version类来比较几个版本。

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. The snapshotInfo Component

4.3.snapshotInfo组件

The snapshotInfo component isn’t used while comparing two versions:

在比较两个版本时不使用snapshotInfo组件。

Version version1_1_snapshot = new Version(1, 1, 0, "snapshot", null, null); 
assertEquals(0, version1_1.compareTo(version1_1_snapshot));

Additionally, the Version class provides the isSnapshot method to check if the version contains a snapshot component:

此外,Version类提供了isSnapshot方法来检查版本是否包含一个快照组件。

assertTrue(version1_1_snapshot.isSnapshot());

4.4. The groupId and artifactId Components

4.4.groupIdartifactId组件

Also, this class compares the lexical order of the groupId and artifactId version components:

另外,这个类比较了groupIdartifactIdversion组件的词法顺序

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. Using Semver4J

5.使用Semver4J

The Semver4j library allows us to follow the rules of the semantic versioning specification in Java.

Semver4j库允许我们在Java中遵循语义版本管理规范的规则。

5.1. Maven Dependency

5.1.Maven的依赖性

First, we’ll add the latest semver4j Maven dependency:

首先,我们要添加最新的semver4jMaven依赖。

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

5.2. Semver

5.2.Semver

Then, we can use the Semver class to define a version:

然后,我们可以使用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);

Internally, it parses a version into components like Major, Minor, and Patch.

在内部,它将一个版本解析为MajorMinorPatch等组件。

5.3. Version Comparison

5.3.版本比较

Also, the Semver class comes with various built-in methods like isGreaterThan, isLowerThan, and isEqualTo for version comparison:

此外,Semver类带有各种内置方法,如isGreaterThanisLowerThanisEqualTo用于版本比较。

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"));

Likewise, it provides the diff method that returns the main difference between the two versions:

同样,它提供了diff方法,返回两个版本之间的主要差异。

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. Version Stability

5.4 版本稳定性

Also, the Semver class comes with the isStable method to check the stability of a version, determined by the presence or absence of a suffix:

另外,Semver类带有isStable方法来检查版本的稳定性,由后缀的存在与否决定。

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

6. Custom Solution

6.定制解决方案

We’ve seen a few solutions to compare the version strings. If they don’t work for a specific use-case, we might have to write a custom solution.

我们已经看到了一些比较版本字符串的解决方案。如果它们对一个特定的使用案例不起作用,我们可能要写一个自定义的解决方案。

Here’s a simple example that works for some basic cases — it can always be extended if we need something more.

这里有一个简单的例子,适用于一些基本情况–如果我们需要更多的东西,它总是可以被扩展。

The idea here is to tokenize the version strings using a dot delimiter, and then compare integer conversion of every String token, beginning from the left. If the token’s integer value is the same, examine the next token, continuing this step until we find a difference (or until we reach the last token in either string):

这里的想法是使用点分隔符对版本字符串进行标记,然后比较每个String标记的整数转换,从左边开始。如果标记的整数值相同,则检查下一个标记,继续这一步骤,直到我们发现一个差异(或直到我们到达两个字符串中的最后一个标记)。

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;
}

Let’s verify our solution by comparing a few versions:

让我们通过比较几个版本来验证我们的解决方案。

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"));

This code has a limitation that it can only compare a version number made of integers delimited by dots.

这段代码有一个局限性,即它只能比较一个由点划定的整数组成的版本号。

Therefore, for comparing alphanumeric version strings, we can use a regular expression to segregate alphabets and compare the lexical order.

因此,对于比较字母数字版本的字符串,我们可以使用正则表达式来隔离字母并比较词序。

7. Conclusion

7.结语

In this article, we looked into various ways to compare version strings in Java.

在这篇文章中,我们研究了在Java中比较版本字符串的各种方法。

At first, we examined built-in solutions provided by build frameworks like Maven and Gradle, using the maven-artifact and gradle-core dependencies, respectively. Then, we explored version comparison features of the jackson-core and semver4j libraries.

首先,我们研究了Maven和Gradle等构建框架提供的内置解决方案,分别使用maven-artifactgradle-core依赖项。然后,我们探索了jackson-coresemver4j库的版本比较功能。

Last, we wrote a custom solution for generic version string comparison.

最后,我们为通用版本字符串比较写了一个自定义解决方案。

As usual, all the code implementations are available over on GitHub.

像往常一样,所有的代码实现都可以在GitHub上找到