14-го сентября состоялась презентация Apple, в этот же день произошло не менее важное событие - релиз Java 17.
Среди новых фич подъехал паттерн матчинг для switch
в preview моде JEP 406.
История началась с того, что в jdk 16 расширили instanceof
оператор, который теперь может принимать type pattern и выполнять матчинг по паттерну. Это маленькое изменение позволило упростить типичную конструкцию с проверкой на тип и последующее приведение.
// before
if (o instanceof String) {
String s = (String)o;
... use s ...
}
// after
if (o instanceof String s) {
... use s ...
}
Обычно проверка производится на совпадение среди нескольких типов и пример показывает насколько код далек от идеала.
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
Для таких операций идеально подходил бы switch
, но в силу ограниченности поддержки типов и сравнения только на соответствие константному значению, приходится использовать цепочку if else
.
Разработчики подумали над ситуацией и добавили ряд улучшений:
возможность работы с любым типом
проверка на соответствие паттерну в
case
возможность обрабатывать
null
значения через встроенныйcase
Теперь предыдущий код выглядит так:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
Зачастую, после совпадения типа, нужно делать дополнительные проверки и описывать их внутри, что может приводить к раздуванию кода:
static void test(Object o) {
switch (o) {
case Integer i:
if (i.intValue() > 100) { ... }
if (i.intValue() > 3 && i.intValue() < 7) { ... }
.......
}
}
Для покрытия таких кейсов были введены 2 новых вида паттернов:
guarded patterns
в форматеtype pattern && boolean expression
, которые позволяют дополнять матчинг по типуboolean
выражениемparenthesized patterns
, которые позволяют избегать неочевидности при формировании логики из несколькихboolean
static void test(Object o) {
switch (o) {
case Integer i && i.intValue() > 100 -> { ...}
case (Integer i && i.intValue() > 3) && (i.intValue() < 7) -> { ...}
.......
}
}
Обработка null
Традиционно, switch выбрасывал NullPointerException
если проверяемый объект был null
. Проверку необходимо было реализовывать за пределами блока.
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
Это имело смысл в рамках ограниченной поддержки типов. Но, так как теперь switch работает с любым типом, а case
поддерживают паттерны, разработчики добавили total type pattern
, с помощью которого можно обработать ситуацию с null
.
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
Планы на будущее
поддержка примитивных типов
general
классы смогут объявлятьdeconstruction
паттерны для указания как они могут быть сматченыподдержка AND and OR паттернов
Комментарии (15)
MIKHTheCreator
21.09.2021 20:58+1Определенно "годное" расширение возможностей Java. Вопрос только: насколько все это приживется, и как скоро перейдут на использование Java 17, после многолетнего использование Java8?
Ну а так, 17 версия Java внесла немало полезных возможностей и неплохую оптимизацию.
vba
22.09.2021 15:18-1Java 17: Pattern Matching for switch
Лично у меня язык не поднимается назвать это сопоставлением с образцом, так, немного адекватный switch и все. Ведь тут чего не хватает? Конструкции сопоставления с образцом есть суть выражения(на уровне семантики языка), а switch в Java никак к выражениям не относится. Вот и получилось опять невесть что, как у последователей карго-культа.
vba
22.09.2021 15:30Хм, как то я и не заметил что все таки, в отличие от C#(<8.0) в Java это скорее сопоставление с образцом:
Java:static String formatterPatternSwitch(Object o) {
C#:
return switch (o) {
case Integer i -> //....;
default -> o.toString();
};
}switch (о) {
case Integer i:
.....;
break;
default:
......;
break;
}Хотя вроде в C# 8.0 это все поправят и введут выражения. Интересно.
pim_jewel
11.10.2021 17:11case (Integer i && i.intValue() > 3) && (i.intValue() < 7) -> { ...}
case (Integer i && i.intValue() > 3) && (i.intValue() < 7) -> { ...}
Это точно? Или должно быть
case Integer i && (i.intValue() > 3 && i.intValue() < 7) -> { ...}
?
dbudim Автор
11.10.2021 17:12Все верно, можете посмотреть JEP: 406 секцию Guarded and parenthesized patterns
pim_jewel
11.10.2021 17:17JEP: 406 секцию Guarded and parenthesized patterns
GuardedPattern: PrimaryPattern && ConditionalAndExpression
В данном случае
PrimaryPattern = Integer i
ConditionalAndExpression = (i.intValue() > 3 && i.intValue() < 7)
Значит получаем так, как я написал:
case Integer i && (i.intValue() > 3 && i.intValue() < 7) -> { ...}
dbudim Автор
11.10.2021 17:23We also change the grammar for
instanceof
expressions to:InstanceofExpression: RelationalExpression instanceof ReferenceType RelationalExpression instanceof PrimaryPattern
This change, and the non-terminal
ConditionalAndExpression
in the grammar rule for a guarded pattern, ensure that, for example, the expressione instanceof String s && s.length() > 1
continues to unambiguously parse as the expression(e instanceof String s) && (s.length() > 1)
. If the trailing&&
is intended to be part of a guarded pattern then the entire pattern should be parenthesized, e.g.,e instanceof (String s && s.length() > 1)
.i.intValue() > 3
в данном примере является частью guarded паттерна(Integer i && i.intValue() > 3)
pim_jewel
11.10.2021 18:25На самом деле, что в лоб, что по лбу:
public class GuardedSwitch { public static void main( String[] args ) { switchMethod( null ); switchMethod( 1 ); switchMethod( 4 ); switchMethod( 7 ); switchMethod( 10 ); switchMethod( "Hello, world!" ); } private static void switchMethod( Object a ) { System.out.println( switch( a ) { case null -> "NULL"; case Integer i && ( i > 3 && i < 7) -> "(3 .. 7)"; case ( Integer i && i >= 7) && ( i < 9 ) -> "[7 .. 9)"; case Integer i && i >= 0 && i <= 3 -> "[0 .. 3]"; case Integer i -> "Another Integer = " + i; default -> "Another object type: " + a.getClass().getTypeName() + " = " + a; } ); } }
Вывод:
NULL [0 .. 3] (3 .. 7) [7 .. 9) Another Integer = 10 Another object type: java.lang.String = Hello, world!
tonhead
Это прекрасно. Осталось только прод мигрировать с 8 версии
Upd: вроде можно же писать код для версии языка X и компилировать в версию языка Y, где X > Y?
dbudim Автор
Да, 8ка наше все. Думаю что очень много проектов еще на ней.
Кстати, в свое время мигрировали с 8 на 11 без особых проблем)
aleksandy
Можно, но это путь ко всевозможным NoClassDefFoundError, NoSuchFieldError, NoSuchMethodError и прочим LinkageError-ам.
Maccimo
Можно взять для этого Jabel от bsideup.
Для pattern matching такой вариант не подойдёт так как нужна поддержка от стандартной библиотеки (см. SwitchBootstraps), но для чистого синтаксического сахара а-ля многострочные строковые литералы — вполне.