Перейти к основному содержимому

Сопоставление шаблонов для коммутатора

· 7 мин. чтения

1. Обзор

В выпуске Java SE 17 в качестве функции предварительного просмотра представлено сопоставление с образцом для выражений и операторов switch ( JEP 406 ). Сопоставление с образцом дает нам больше гибкости при определении условий для случаев переключения .

В дополнение к меткам case, которые теперь могут содержать шаблоны, выражение селектора больше не ограничивается несколькими типами. До сопоставления с образцом случаи переключения поддерживали только простую проверку выражения селектора, которое должно точно соответствовать постоянному значению.

В этом руководстве мы рассмотрим три различных типа шаблонов, которые можно применять в операторах switch . Мы также рассмотрим некоторые особенности коммутатора , такие как покрытие всех значений, упорядочивание подклассов и обработка нулевых значений.

2. Заявление о переключении

Мы используем переключатель в Java для передачи управления одному из нескольких предопределенных операторов case. Какой оператор будет выбран, зависит от значения выражения селектора переключателя .

В более ранних версиях Java выражение селектора должно было быть числом, строкой или константой . Кроме того, метки case могут содержать только константы:

final String b = "B";
switch (args[0]) {
case "A" -> System.out.println("Parameter is A");
case b -> System.out.println("Parameter is b");
default -> System.out.println("Parameter is unknown");
};

В нашем примере, если бы переменная b не была final , компилятор выдал бы ошибку, требующую константного выражения.

3. Сопоставление с образцом

Сопоставление с образцом, как правило, впервые было представлено в качестве функции предварительного просмотра в Java SE 14.

Он был ограничен только одной формой узора – шрифтовым узором. Типичный шаблон состоит из имени типа и переменной, к которой привязывается результат.

Применение шаблонов типов к оператору instanceof упрощает проверку и приведение типов . Более того, это позволяет нам объединить их в одно выражение:

if (o instanceof String s) {
System.out.printf("Object is a string %s", s);
} else if (o instanceof Number n) {
System.out.printf("Object is a number %n", n);
}

Это встроенное усовершенствование языка помогает нам писать меньше кода с улучшенной читабельностью.

4. Шаблоны для переключения

Сопоставление с образцом для instanceof стало постоянной функцией в Java SE 16.

В Java 17 применение сопоставления с образцом теперь также расширяется до переключения выражений .

Тем не менее, это все еще функция предварительного просмотра , поэтому нам нужно включить предварительный просмотр, чтобы использовать ее:

java --enable-preview --source 17 PatternMatching.java

4.1. Тип шаблона

Давайте посмотрим, как шаблоны типов и оператор instanceof могут применяться в операторах switch .

В качестве примера мы создадим метод, который преобразует различные типы в double с помощью операторов if-else . Наш метод просто вернет ноль, если тип не поддерживается:

static double getDoubleUsingIf(Object o) {
double result;
if (o instanceof Integer) {
result = ((Integer) o).doubleValue();
} else if (o instanceof Float) {
result = ((Float) o).doubleValue();
} else if (o instanceof String) {
result = Double.parseDouble(((String) o));
} else {
result = 0d;
}
return result;
}

Мы можем решить ту же проблему с меньшим количеством кода, используя шаблоны типов в switch :

static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0d;
};
}

В более ранних версиях Java выражение селектора было ограничено несколькими типами. Однако с шаблонами типов выражение селектора переключателя может быть любого типа.

4.2. Охраняемый шаблон

Шаблоны типов помогают нам передавать управление на основе определенного типа. Однако иногда нам также необходимо выполнить дополнительные проверки переданного значения.

Например, мы можем использовать оператор if для проверки длины строки :

static double getDoubleValueUsingIf(Object o) {
return switch (o) {
case String s -> {
if (s.length() > 0) {
yield Double.parseDouble(s);
} else {
yield 0d;
}
}
default -> 0d;
};
}

Мы можем решить ту же проблему, используя защищенные шаблоны. Они используют комбинацию шаблона и логического выражения:

static double getDoubleValueUsingGuardedPatterns(Object o) {
return switch (o) {
case String s && s.length() > 0 -> Double.parseDouble(s);
default -> 0d;
};
}

Защищенные шаблоны позволяют избежать дополнительных условий if в операторах switch . Вместо этого мы можем переместить нашу условную логику на метку case .

4.3. Шаблон в скобках

В дополнение к условной логике в метке case, шаблоны в скобках позволяют нам группировать их .

Мы можем просто использовать круглые скобки в наших логических выражениях при выполнении дополнительных проверок:

static double getDoubleValueUsingParenthesizedPatterns(Object o) {
return switch (o) {
case String s && s.length() > 0 && !(s.contains("#") || s.contains("@")) -> Double.parseDouble(s);
default -> 0d;
};
}

Используя круглые скобки, мы можем избежать дополнительных операторов if-else .

5. Особенности переключения

Давайте теперь рассмотрим пару конкретных случаев, которые следует учитывать при использовании сопоставления с образцом в switch .

5.1. Охватывая все ценности

При использовании сопоставления с образцом в switch компилятор Java проверит покрытие типов .

Давайте рассмотрим пример условия переключения , принимающего любой объект, но охватывающего только случай String :

static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case String s -> Double.parseDouble(s);
};
}

Наш пример приведет к следующей ошибке компиляции:

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[10,16] the switch expression does not cover all possible input values

Это связано с тем, что метки case case должны включать тип выражения селектора . ****

Метка case по умолчанию также может применяться вместо определенного типа селектора.

5.2. Заказ подклассов

При использовании подклассов с сопоставлением с образцом в switch порядок случаев имеет значение .

Давайте рассмотрим пример, в котором регистр String идет после случая CharSequence .

static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case CharSequence c -> Double.parseDouble(c.toString());
case String s -> Double.parseDouble(s);
default -> 0d;
};
}

Поскольку String является подклассом CharSequence, наш пример приведет к следующей ошибке компиляции:

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[12,18] this case label is dominated by a preceding case label

Причина этой ошибки заключается в том, что нет никаких шансов, что выполнение перейдет ко второму варианту, поскольку любой строковый объект, переданный методу, будет обрабатываться в самом первом случае.

5.3. Обработка нулевых значений

В более ранних версиях Java каждая передача нулевого значения оператору switch приводила к исключению NullPointerException .

Однако с помощью шаблонов типов теперь можно применять проверку null как отдельную метку case :

static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case String s -> Double.parseDouble(s);
case null -> 0d;
default -> 0d;
};
}

Если нет метки случая, специфичной для null, метка шаблона общего типа будет соответствовать нулевым значениям :

static double getDoubleUsingSwitchTotalType(Object o) {
return switch (o) {
case String s -> Double.parseDouble(s);
case Object ob -> 0d;
};
}

Мы должны отметить, что выражение переключения не может иметь как нулевой регистр, так и общий регистр типов.

Такой оператор switch приведет к следующей ошибке компиляции:

[ERROR] Failed to execute goal ... on project core-java-17: Compilation failure
[ERROR] /D:/Projects/.../HandlingNullValuesUnitTest.java:[14,13] switch has both a total pattern and a default label

Наконец, оператор switch , использующий сопоставление с образцом, может по-прежнему вызывать исключение NullPointerException .

Однако это возможно только в том случае, если блок switch не имеет метки case, совпадающей с нулевым значением.

6. Заключение

В этой статье мы рассмотрели сопоставление с образцом для выражений и операторов switch — функцию предварительного просмотра в Java SE 17 . Мы видели, что при использовании шаблонов в метках case этот выбор определяется сопоставлением с шаблоном, а не простой проверкой на равенство.

В примерах мы рассмотрели три различных типа шаблонов, которые можно применять в операторах switch . Наконец, мы рассмотрели пару конкретных случаев, включая охват всех значений, упорядочивание подклассов и обработку нулевых значений.

Как всегда, полный исходный код доступен на GitHub .