Versionsvergleich in Java

1. Übersicht

Mit der Weiterentwicklung der DevOps-Technologien ist es üblich, eine Anwendung mehrmals am Tag zu erstellen und bereitzustellen.

Daher wird jedem Build eine eindeutige Versionsnummer zugewiesen, damit wir zwischen Builds unterscheiden können . Manchmal besteht die Notwendigkeit, die Versionszeichenfolgen programmgesteuert zu vergleichen.

In diesem Artikel werden einige Möglichkeiten zum Vergleichen von Versionszeichenfolgen in Java über verschiedene Bibliotheken untersucht. Zuletzt werden wir ein benutzerdefiniertes Programm schreiben, um den allgemeinen Vergleich von Versionszeichenfolgen durchzuführen.

2. Verwenden von Maven-Artefakten

Lassen Sie uns zunächst untersuchen, wie Maven mit dem Versionsvergleich umgeht.

2.1. Maven-Abhängigkeit

Zuerst fügen wir unserer pom.xml die neueste Maven-Artefakt- Maven-Abhängigkeit hinzu :

 org.apache.maven maven-artifact 3.6.3 

2.2. Vergleichbare Version

Lassen Sie uns die ComparableVersion- Klasse untersuchen. Es bietet eine generische Implementierung des Versionsvergleichs mit einer unbegrenzten Anzahl von Versionskomponenten .

Es enthält eine compareTo- Methode, und das Ergebnis des Vergleichs ist größer oder kleiner als 0, wenn eine Version größer oder kleiner als die andere ist:

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

Hier können wir bestätigen, dass die 1.1-Version kleiner als die 1.2-Version und die 1.3-Version größer als die 1.2-Version ist.

Beim Vergleich derselben Versionen erhalten wir jedoch 0:

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

2.3. Versionstrennzeichen und Qualifikationsmerkmale

Darüber hinaus berücksichtigt die ComparableVersion- Klasse den Punkt (.) Und den Bindestrich (-) als Trennzeichen, wobei der Punkt die Haupt- und Nebenversion trennt und der Bindestrich Qualifizierer definiert :

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

Hier können wir bestätigen, dass die 1.1-Version größer als die 1.1-Alpha-Version ist.

Es gibt einige bekannte Qualifikationsmerkmale, die von der ComparableVersion unterstützt werden, wie Alpha , Beta , Meilenstein , RC und Snapshot (in der Reihenfolge vom niedrigsten zum höchsten):

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

Außerdem können wir unbekannte Qualifizierer definieren und ihre Reihenfolge nach den bereits diskutierten bekannten Qualifizierern mit lexikalischer Reihenfolge ohne Berücksichtigung der Groß- und Kleinschreibung berücksichtigen :

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. Gradle-Core verwenden

Wie Maven verfügt auch Gradle über die integrierte Funktion zum Versionsvergleich.

3.1. Maven-Abhängigkeit

Fügen wir zunächst die neueste Gradle-Core- Maven-Abhängigkeit aus dem Gradle Releases-Repo hinzu:

 org.gradle gradle-core 6.1.1 

3.2. Versionsnummer

Die Versionsnummer der Klasse zur Verfügung gestellt von Gradle vergleicht zwei Versionen, ähnlich wie Maven ComparableVersion Klasse:

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); VersionNumber version1_1_0 = VersionNumber.parse("1.1.0"); assertEquals(0, version1_1.compareTo(version1_1_0)); 

3.3. Versionskomponenten

Im Gegensatz zu der ComparableVersion Klasse, die Version unterstützt Klasse nur fünf Version Komponenten - Dur , Moll , Micro , Flecke und 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. Versionsschemata

Außerdem unterstützt VersionNumber verschiedene Versionsschemata wie Major.Minor.Micro-Qualifier und 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);

4. Verwenden von Jackson-Core

4.1. Maven-Abhängigkeit

Fügen wir unserer pom.xml ähnlich wie bei anderen Abhängigkeiten die neueste Jackson-Core- Maven-Abhängigkeit hinzu :

 com.fasterxml.jackson.core jackson-core 2.11.1 

4.2. Ausführung

Anschließend können wir Jacksons Versionsklasse untersuchen , die Versionsinformationen einer Komponente zusammen mit den optionalen Werten groupId und artefaktId enthalten kann .

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

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

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

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:

assertTrue(version1_1_snapshot.isSnapshot());

4.4. The groupId and artifactId Components

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

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

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

5.1. Maven Dependency

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

 com.vdurmont semver4j 3.1.0 

5.2. Semver

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

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

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

5.3. Version Comparison

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

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:

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

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:

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

6. Custom Solution

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

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

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

Zunächst untersuchten wir integrierte Lösungen, die von Build-Frameworks wie Maven und Gradle bereitgestellt wurden, unter Verwendung der Maven-Artefakt- bzw. Gradle-Core- Abhängigkeiten. Anschließend untersuchten wir die Versionsvergleichsfunktionen der Jackson-Core- und Semver4j- Bibliotheken.

Zuletzt haben wir eine benutzerdefinierte Lösung für den Vergleich generischer Versionszeichenfolgen geschrieben.

Wie üblich sind alle Code-Implementierungen über GitHub verfügbar.