1. Обзор
С развитием технологий DevOps стало обычным создавать и развертывать приложение несколько раз в день.
Поэтому каждой сборке присваивается уникальный номер версии, чтобы мы могли различать сборки . Иногда возникает необходимость сравнить строки версий программно.
В этой статье мы рассмотрим несколько способов сравнения строк версий в Java с помощью различных библиотек. Наконец, мы напишем пользовательскую программу для обработки общего сравнения версий и строк.
2. Использование maven-артефакта
Для начала давайте рассмотрим, как Maven обрабатывает сравнение версий.
2.1. Зависимость от Maven
Во- первых, мы добавим последнюю зависимость Maven от maven-artifact
в наш pom.xml
:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.6.3</version>
</dependency>
2.2. ComparableVersion
Давайте рассмотрим класс ComparableVersion
. Он обеспечивает общую реализацию сравнения версий с неограниченным числом компонентов версий .
Он содержит метод 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);
Здесь мы можем подтвердить, что версия 1.1 меньше версии 1.2, а версия 1.3 больше версии 1.2.
Однако в результате мы получим 0 при сравнении одинаковых версий:
ComparableVersion version1_1_0 = new ComparableVersion("1.1.0");
assertEquals(0, version1_1.compareTo(version1_1_0));
2.3. Разделители и квалификаторы версий ``
Кроме того, класс ComparableVersion
учитывает точку (.) и дефис (-) в качестве разделителей, где точка разделяет основную и дополнительную версии, а дефис определяет квалификаторы :
ComparableVersion version1_1_alpha = new ComparableVersion("1.1-alpha");
assertTrue(version1_1.compareTo(version1_1_alpha) > 0);
Здесь мы можем подтвердить, что версия 1.1 лучше, чем альфа-версия 1.1.
ComparableVersion
поддерживает несколько известных квалификаторов, таких как alpha
, beta
, milestone
, RC
и snapshot
(в порядке от низшего к высшему):
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);
Кроме того, это позволяет нам определять неизвестные квалификаторы и соблюдать их порядок после уже обсуждавшихся известных квалификаторов с нечувствительным к регистру лексическим порядком :
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. Использование ядра градиента
Как и Maven, Gradle также имеет встроенную возможность сравнения версий.
3.1. Зависимость от Maven
Во-первых, давайте добавим последнюю зависимость Maven от ядра gradle
из репозитория Gradle Releases :
<dependency>
<groupId>org.gradle</groupId>
<artifactId>gradle-core</artifactId>
<version>6.1.1</version>
</dependency>
3.2. Номер версии
Класс VersionNumber
, предоставленный Gradle, сравнивает две версии, подобно классу 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. Компоненты версии
В отличие от класса 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. Схемы версий
Кроме того, VersionNumber
поддерживает несколько разных схем версий, таких как 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);
4. Использование ядра Джексона
4.1. Зависимость от Maven
Как и в случае с другими зависимостями, давайте добавим последнюю зависимость jackson -core
Maven в наш pom.xml
:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.1</version>
</dependency>
4.2. Версия
Затем мы можем изучить класс версии
Джексона , который может содержать информацию о версиях компонента вместе с необязательными значениями groupId
и ArtiftId
. ``
Таким образом, конструктор класса Version
позволяет нам определять groupId
и ArtiftId ,
а также такие компоненты, как Major
, Minor
и Patch
:
public Version (int major, int minor, int patchLevel, String snapshotInfo, String groupId, String artifactId) {
//...
}
Итак, давайте сравним несколько версий, используя класс 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. Компонент snapshotInfo
_
Компонент snapshotInfo
не используется при сравнении двух версий:
Version version1_1_snapshot = new Version(1, 1, 0, "snapshot", null, null);
assertEquals(0, version1_1.compareTo(version1_1_snapshot));
Кроме того, класс Version предоставляет метод
isSnapshot
для проверки наличия в версии компонента моментального снимка:
assertTrue(version1_1_snapshot.isSnapshot());
4.4. Компоненты groupId
и ArtiftId
_
Также этот класс сравнивает лексический порядок компонентов версии groupId
и ArtiftId
:
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
Библиотека Semver4j
позволяет нам следовать правилам спецификации семантической версии в Java.
5.1. Зависимость от Maven
Во-первых, мы добавим последнюю зависимость semver4j
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);
Внутри он анализирует версию на такие компоненты, как Major
, Minor
и Patch
.
5.3. Сравнение версий
Кроме того, класс Semver
поставляется с различными встроенными методами, такими как isGreaterThan
, isLowerThan
и isEqualTo
для сравнения версий:
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"));
Точно так же он предоставляет метод 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. Стабильность версии
Кроме того, класс Semver
поставляется с методом isStable
для проверки стабильности версии, определяемой наличием или отсутствием суффикса:
assertTrue(version1_1.isStable());
assertFalse(version1_1_alpha.isStable());
6. Индивидуальное решение
Мы видели несколько решений для сравнения строк версий. Если они не работают для конкретного варианта использования, возможно, нам придется написать собственное решение.
Вот простой пример, который работает для некоторых основных случаев — его всегда можно расширить, если нам нужно что-то еще.
Идея здесь состоит в том, чтобы разбить строки версии с помощью разделителя точек, а затем сравнить целочисленное преобразование каждого токена 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;
}
Давайте проверим наше решение, сравнив несколько версий:
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"));
Этот код имеет ограничение, заключающееся в том, что он может сравнивать только номер версии, состоящий из целых чисел, разделенных точками.
Следовательно, для сравнения буквенно-цифровых строк версий мы можем использовать регулярное выражение для разделения алфавитов и сравнения лексического порядка.
7. Заключение
В этой статье мы рассмотрели различные способы сравнения строк версий в Java.
Сначала мы рассмотрели встроенные решения, предоставляемые фреймворками сборки, такими как Maven и Gradle, с использованием зависимостей maven-artifact
и gradle-core
соответственно. Затем мы изучили функции сравнения версий библиотек jackson -core
и semver4j
.
Наконец, мы написали собственное решение для сравнения строки общей версии.
Как обычно, все реализации кода доступны на GitHub .