JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)
Primitive Types in Patterns (примитивные типы в шаблонах), instanceof и switch, пятая версия Preview (предварительная версия)
| Ответственный | Angelos Bimpoudis |
| Тип | Feature |
| Область | SE |
| Статус | Closed / Delivered |
| Выпуск | 27 |
| Компонент | specification / language |
| Обсуждение | amber dash dev at openjdk dot org |
| Трудоёмкость | M |
| Длительность | M |
| Связан с | JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview) |
| Рецензенты | Alex Buckley, Brian Goetz |
| Одобрен | Brian Goetz |
| Создан | 2026/03/05 13:37 |
| Обновлён | 2026/07/08 15:23 |
| Задача | 8379318 |
Аннотация
Расширить Pattern Matching (сопоставление с образцом): разрешить примитивные типы во всех контекстах шаблонов, а также расширить instanceof и switch для работы со всеми примитивными типами. Это языковая возможность в статусе Preview.
История
Эта возможность была впервые предложена в JEP 455 (JDK 23) и без изменений повторно выходила в статусе Preview в JEP 488 (JDK 24) и JEP 507 (JDK 25). В JEP 530 (JDK 26) она снова вышла в статусе Preview с двумя изменениями: улучшено определение безусловной точности и введены более строгие проверки доминирования в конструкциях switch. Здесь мы предлагаем выпустить её в статусе Preview в пятый раз, без изменений.
Цели
-
Обеспечить единообразное исследование данных: разрешить шаблоны типов для всех типов, как примитивных, так и ссылочных.
-
Согласовать шаблоны типов с
instanceof, аinstanceof— с безопасным приведением типов. -
Разрешить Pattern Matching использовать примитивные типы как во вложенных контекстах шаблонов, так и в контекстах верхнего уровня.
-
Предоставить простые в использовании конструкции, которые устраняют риск потери информации из-за небезопасных приведений типов.
-
Вслед за улучшениями
switchв Java 5 (switchпо enum) и Java 7 (switchпо строкам) разрешитьswitchобрабатывать значения любого примитивного типа.
Что не является целью
- Добавление в язык Java новых видов преобразований не является целью.
Мотивация
Ряд ограничений, связанных с примитивными типами, мешает использованию Pattern Matching, instanceof и switch. Если устранить эти ограничения, язык станет более единообразным и выразительным.
Pattern Matching для switch не поддерживает шаблоны примитивных типов
Первое ограничение состоит в том, что Pattern Matching для switch (JEP 441) не поддерживает шаблоны примитивных типов, то есть шаблоны типов, в которых указан примитивный тип. В switch поддерживаются только шаблоны типов, в которых указан ссылочный тип, например case Integer i или case String s, и шаблоны, в которых указан record-класс (JEP 440), например case Point(int x, int y).
Если бы в switch поддерживались шаблоны примитивных типов, мы могли бы улучшить выражение switch
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
превратив ветку default в ветку case с шаблоном примитивного типа, который делает доступным сопоставленное значение:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
Поддержка шаблонов примитивных типов также позволила бы guard-условиям проверять сопоставленное значение:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
Record Patterns (шаблоны записей) ограниченно поддерживают примитивные типы
Ещё одно ограничение состоит в том, что Record Patterns ограниченно поддерживают примитивные типы. Record Patterns упрощают обработку данных, разбирая record-объект на отдельные компоненты. Если компонент — примитивное значение, record-шаблон должен точно указывать тип этого значения. Это неудобно для разработчиков и не согласуется с тем, что в остальных частях языка есть удобные автоматические преобразования.
Например, пусть нам нужно обрабатывать данные JSON, представленные такими record-классами:
sealed interface JsonValue {
record JsonString(String s) implements JsonValue { }
record JsonNumber(double d) implements JsonValue { }
record JsonObject(Map<String, JsonValue> map) implements JsonValue { }
}
JSON не различает целые и нецелые числа, поэтому JsonNumber для максимальной гибкости представляет число компонентом типа double. При создании record-объекта JsonNumber не обязательно передавать double: можно передать int, например 30, и компилятор автоматически выполнит расширение int до double:
var json = new JsonObject(Map.of("name", new JsonString("John"),
"age", new JsonNumber(30)));
К сожалению, при разборе JsonNumber с помощью record-шаблона компилятор не столь услужлив. Поскольку JsonNumber объявлен с компонентом типа double, мы должны разбирать JsonNumber относительно double и преобразовывать в int вручную:
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String n)
&& map.get("age") instanceof JsonNumber(double a)) {
int age = (int)a; // unavoidable (and potentially lossy!) cast
}
Иными словами, шаблоны примитивных типов можно вкладывать в record-шаблоны, но они инвариантны: примитивный тип в шаблоне должен совпадать с примитивным типом компонента record-класса. Нельзя разобрать JsonNumber через instanceof JsonNumber(int age) так, чтобы компилятор автоматически выполнил сужение компонента double до int.
Причина этого ограничения в том, что сужение может приводить к потерям: значение компонента double во время выполнения может оказаться слишком большим или иметь слишком высокую точность для переменной int. Однако ключевое преимущество Pattern Matching в том, что недопустимые значения отклоняются автоматически — просто за счёт несовпадения. Если компонент double объекта JsonNumber слишком велик или слишком точен, чтобы безопасно сузить его до int, то instanceof JsonNumber(int age) может просто вернуть false, и программа обработает большой компонент double в другой ветке.
Поддержка шаблонов примитивных типов позволила бы снять это ограничение. Pattern Matching мог бы защищать от сужающего преобразования значения в примитивный тип, которое может приводить к потерям, как на верхнем уровне, так и внутри record-шаблонов. Поскольку любой double можно преобразовать в int, шаблон примитивного типа int a был бы применим к соответствующему компоненту JsonNumber типа double. Если и только если компонент double можно преобразовать в int без потери информации, instanceof совпадёт с шаблоном и будет выбрана ветка if, в области видимости которой находится локальная переменная a:
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String n)
&& map.get("age") instanceof JsonNumber(int a)) {
... n ...
... a ...
}
Благодаря этому вложенные шаблоны примитивных типов работали бы так же гладко, как вложенные шаблоны ссылочных типов.
Pattern Matching для instanceof не поддерживает шаблоны примитивных типов
Ещё одно ограничение состоит в том, что Pattern Matching для instanceof (JEP 394) не поддерживает шаблоны примитивных типов. В instanceof поддерживаются только шаблоны типов, в которых указан ссылочный тип или record-класс.
Шаблоны примитивных типов были бы так же полезны в instanceof, как и в switch. Назначение instanceof, в широком смысле, — проверить, можно ли безопасно преобразовать значение в заданный тип; именно поэтому instanceof и операции приведения типов всегда встречаются рядом. Для примитивных типов такая проверка критически важна, потому что при преобразовании примитивных значений из одного типа в другой возможна потеря информации.
Например, преобразование значения int в float выполняется автоматически оператором присваивания, хотя оно может приводить к потерям, — и мы не получаем об этом никакого предупреждения:
int getPopulation() {...}
float pop = getPopulation(); // silent potential loss of information
В то же время преобразование значения int в byte выполняется явным приведением, но это приведение может приводить к потерям, поэтому перед ним нужна трудоёмкая проверка диапазона:
if (i >= -128 && i <= 127) {
byte b = (byte)i;
... b ...
}
Шаблоны примитивных типов в instanceof охватили бы встроенные в язык преобразования с потерями и избавили бы от кропотливых проверок диапазона, которые мы пишем вручную уже три десятилетия. Иными словами, instanceof мог бы проверять не только типы, но и значения. Два примера выше можно было бы переписать так:
if (getPopulation() instanceof float pop) {
... pop ...
}
if (i instanceof byte b) {
... b ...
}
Оператор instanceof сочетает удобство оператора присваивания с безопасностью Pattern Matching. Если входное значение можно безопасно преобразовать в тип из шаблона примитивного типа, шаблон совпадает и результат преобразования сразу доступен. Но если при преобразовании будет потеряна информация, шаблон не совпадает, и программа должна обработать недопустимое входное значение в другой ветке.
Примитивные типы в instanceof и switch
Если мы снимаем ограничения, связанные с шаблонами примитивных типов, было бы полезно снять и связанное с ними ограничение: когда instanceof принимает тип, а не шаблон, он принимает только ссылочный тип, но не примитивный. Если бы instanceof мог принимать примитивный тип, он проверял бы, безопасно ли преобразование, но не выполнял бы его:
if (i instanceof byte) { // value of i fits in a byte
... (byte)i ... // traditional cast required
}
Это улучшение instanceof восстанавливает согласованность семантики instanceof T и instanceof T t, которая была бы утрачена, если бы мы разрешили примитивные типы в одном контексте, но не в другом.
Наконец, было бы полезно снять ограничение, из-за которого switch может принимать значения byte, short, char и int, но не значения boolean, float, double или long.
Switch по значениям boolean был бы полезной альтернативой тернарному условному оператору (?:), потому что boolean switch может содержать не только выражения, но и операторы. Например, следующий код использует boolean switch, чтобы выполнить журналирование при false:
startProcessing(OrderStatus.NEW, switch (user.isLoggedIn()) {
case true -> user.id();
case false -> { log("Unrecognized user"); yield -1; }
});
Switch по значениям long позволил бы использовать в метках case константы long, и не пришлось бы обрабатывать большие константы отдельными операторами if:
long v = ...;
switch (v) {
case 1L -> ...;
case 2L -> ...;
case 10_000_000_000L -> ...;
case 20_000_000_000L -> ...;
case long x -> ... x ...;
}
Описание
Сейчас шаблоны примитивных типов разрешены только как вложенные шаблоны в record-шаблонах и только тогда, когда они точно называют тип кандидата на сопоставление, например:
v instanceof JsonNumber(double a)
Чтобы поддержать более единообразное исследование данных кандидата на сопоставление v с помощью Pattern Matching, мы:
-
Расширим Pattern Matching так, чтобы шаблоны примитивных типов были применимы к более широкому кругу типов кандидатов на сопоставление. Это позволит писать выражения вида
v instanceof JsonNumber(int age). -
Улучшим конструкции
instanceofиswitch, чтобы они поддерживали шаблоны примитивных типов в качестве шаблонов верхнего уровня. -
Дополнительно улучшим конструкцию
instanceof, чтобы при использовании для проверки типа, а не для сопоставления с шаблоном, она могла проверять на соответствие всем типам, а не только ссылочным. Так текущая рольinstanceofкак предусловия безопасного приведения ссылочных типов распространится на все типы.В более широком смысле это означает, что
instanceofможет защищать все преобразования — проверяется ли тип кандидата на сопоставление (например,x instanceof intилиy instanceof String) или сопоставляется его значение (например,x instanceof int iилиy instanceof String s). -
Дополнительно улучшим конструкцию
switch, чтобы она работала со всеми примитивными типами, а не только с частью целочисленных примитивных типов.
Мы реализуем эти изменения, изменив небольшое число правил языка, регулирующих использование примитивных типов, и определив, когда преобразование из одного типа в другой безопасно, — для этого нужно знать преобразуемое значение, а также исходный и целевой типы преобразования.
Это Preview-возможность языка, по умолчанию она отключена
Чтобы опробовать описанные здесь изменения, нужно включить Preview-возможности:
-
скомпилируйте программу с
javac --release 27 --enable-preview Main.javaи запустите её сjava --enable-preview Main; или -
при использовании средства запуска исходного кода запустите программу с
java --enable-preview Main.java; или -
при использовании
jshellзапустите его сjshell --enable-preview.
Безопасность преобразований
Преобразование является точным, если во время выполнения не происходит потери информации; в противном случае оно неточное. Точность преобразования зависит от исходного типа, целевого типа и, возможно, от входного значения.
Два примера преобразований, которые точны или неточны в зависимости от входного значения, — long в int (сужающее примитивное преобразование) и int в float (расширяющее примитивное преобразование): оба теряют точность для некоторых входных значений. Ещё один пример — преобразование из Object в String, которое точно или неточно в зависимости от того, является ли входное значение динамически String, хотя это преобразование не может терять точность. Во всех этих случаях во время выполнения нужно проверять, будет ли преобразование, если его выполнить, точным, то есть можно ли преобразовать значение из исходного типа в целевой без потери информации или, если бы выполнялось приведение, без выбрасывания исключения. (Точность преобразования long в int проверяется числовым равенством (==), а точность преобразования int в float — с помощью эквивалентности представлений.)
Напротив, для некоторых преобразований уже во время компиляции известно, что во время выполнения они не потеряют информацию независимо от входного значения. Для такого преобразования, которое называется безусловно точным, проверка во время выполнения не нужна. Есть две категории таких преобразований:
-
Безусловно точное преобразование на основе значения — это преобразование, при котором входное значение является константным выражением и
-
преобразование является либо сужающим примитивным преобразованием, либо расширяющим и сужающим примитивным преобразованием, либо одним из расширяющих примитивных преобразований, которые могут терять точность, например
intвfloat, и -
проверка во время компиляции определяет, что преобразование значения не приводит к потере информации.
Например, сужение значения
42типаintдоbyteбезусловно точно, как и расширение значения4096типаintдоfloat. -
В следующей таблице показаны преобразования, разрешённые между примитивными типами. Безусловно точные преобразования для значений, не являющихся константными выражениями, обозначены символом ɛ. Символ ≈ означает тождественное преобразование, ω — расширяющее примитивное преобразование, η — сужающее примитивное преобразование, а ωη — расширяющее и сужающее примитивное преобразование. Символ — означает, что преобразование не разрешено.
| В → | byte |
short |
char |
int |
long |
float |
double |
boolean |
|---|---|---|---|---|---|---|---|---|
| Из ↓ | ||||||||
byte |
≈ |
ɛ |
ωη |
ɛ |
ɛ |
ɛ |
ɛ |
— |
short |
η |
≈ |
η |
ɛ |
ɛ |
ɛ |
ɛ |
— |
char |
η |
η |
≈ |
ɛ |
ɛ |
ɛ |
ɛ |
— |
int |
η |
η |
η |
≈ |
ɛ |
ω |
ɛ |
— |
long |
η |
η |
η |
η |
≈ |
ω |
ω |
— |
float |
η |
η |
η |
η |
η |
≈ |
ɛ |
— |
double |
η |
η |
η |
η |
η |
η |
≈ |
— |
boolean |
— |
— |
— |
— |
— |
— |
— |
≈ |
Сравнив эту таблицу с её аналогом в JLS §5.5, можно увидеть, что многие преобразования, обозначенные в JLS §5.5 как ω, повышены до безусловно точных ɛ выше.
instanceof как предварительное условие безопасного приведения
Проверки типа с помощью instanceof традиционно ограничены ссылочными типами. Классический смысл instanceof — проверка предварительного условия, которая отвечает на вопрос: безопасно ли привести это значение к этому типу? Для примитивных типов этот вопрос ещё актуальнее, чем для ссылочных. Для ссылочных типов, если проверку случайно пропустить, выполнение небезопасного приведения, скорее всего, не причинит вреда: будет выброшено ClassCastException, и неправильно приведённое значение нельзя будет использовать. Для примитивных же типов, где нет удобного способа проверить безопасность, выполнение небезопасного приведения, скорее всего, приведёт к трудноуловимым ошибкам. Вместо того чтобы выбросить исключение, оно может незаметно потерять информацию, например величину, знак или точность, и неправильно приведённое значение попадёт в остальную часть программы.
Чтобы разрешить примитивные типы в операторе проверки типа instanceof, мы снимаем ограничения (JLS §15.20.2), согласно которым тип левого операнда должен быть ссылочным типом, а правый операнд должен задавать ссылочный тип. Синтаксис оператора проверки типа становится таким:
InstanceofExpression:
RelationalExpression instanceof Type
...
Во время выполнения мы распространяем instanceof на примитивные типы, опираясь на точные преобразования: если значение в левой части можно преобразовать к типу в правой части с помощью точного преобразования, то привести значение к этому типу было бы безопасно, и instanceof возвращает true.
Вот несколько примеров того, как расширенный instanceof может защитить приведение. Безусловно точные преобразования возвращают true независимо от входного значения; все остальные преобразования требуют проверки во время выполнения, результат которой показан.
byte b = 42;
b instanceof int; // true (unconditionally exact)
int i = 42;
i instanceof byte; // true (exact)
int i = 1000;
i instanceof byte; // false (not exact)
int i = 16_777_217; // 2^24 + 1
i instanceof float; // false (not exact)
i instanceof double; // true (unconditionally exact)
i instanceof Integer; // true (unconditionally exact)
i instanceof Number; // true (unconditionally exact)
float f = 1000.0f;
f instanceof byte; // false
f instanceof int; // true (exact)
f instanceof double; // true (unconditionally exact)
double d = 1000.0d;
d instanceof byte; // false
d instanceof int; // true (exact)
d instanceof float; // true (exact)
Integer ii = 1000;
ii instanceof int; // true (exact)
ii instanceof float; // true (exact)
ii instanceof double; // true (exact)
Integer ii = 16_777_217;
ii instanceof float; // false (not exact)
ii instanceof double; // true (exact)
Мы не добавляем в язык новых преобразований, не меняем существующие преобразования и не меняем то, какие преобразования разрешены в существующих контекстах, например при присваивании. Применимость instanceof к данному значению и типу определяется тем, разрешено ли преобразование в контексте приведения и является ли оно точным. Например, b instanceof char никогда не разрешено, если b — переменная типа boolean, потому что преобразования приведения из boolean в char не существует.
Шаблоны примитивных типов в instanceof и switch
Шаблон типа объединяет проверку типа с условным преобразованием. Благодаря этому при успешной проверке типа не нужно явное приведение, а неприведённое значение можно обработать в другой ветви, если проверка типа не прошла. Когда оператор проверки типа instanceof поддерживал только ссылочные типы, было естественно разрешать в instanceof и switch только шаблоны ссылочных типов; теперь, когда оператор проверки типа instanceof поддерживает примитивные типы, естественно разрешить шаблоны примитивных типов в instanceof и switch.
Для этого мы снимаем ограничение, согласно которому примитивные типы нельзя использовать в шаблоне типа верхнего уровня. В результате громоздкий код
int i = 1000;
if (i instanceof byte) { // false -- i cannot be converted exactly to byte
byte b = (byte)i;
... b ...
}
можно записать как
if (i instanceof byte b) {
... b ...
}
потому что i instanceof byte b означает «проверить, выполняется ли i instanceof byte, и если да, привести i к byte и связать это значение с b».
Семантика шаблонов типов определяется тремя предикатами: применимостью, безусловностью и сопоставлением. Мы снимаем ограничения на обработку шаблонов примитивных типов следующим образом:
-
Применимость определяет, допустим ли шаблон во время компиляции.
Раньше для применимости шаблона примитивного типа требовалось, чтобы кандидат на сопоставление имел в точности тот же тип, что и тип в шаблоне. Например,
switch (... an int ...) { case double d: ... }не допускалось, потому что шаблонdoubleне был применим кint.Теперь шаблон примитивного типа для типа
Tприменим к кандидату на сопоставление типаU, еслиUможно привести кTбез предупреждения о непроверяемом приведении. Посколькуintможно привести кdouble, этотswitchтеперь допустим. -
Раньше шаблоны примитивных типов были применимы только к кандидатам на сопоставление того же типа, поэтому все такие шаблоны были безусловными.
Теперь шаблон примитивного типа для типа
Tявляется безусловным для кандидата на сопоставление типаU, если преобразование изUвTбезусловно точное. -
Для условных шаблонов типов сопоставление определяет, успешны ли необходимые проверки во время выполнения.
Раньше значение
v, отличное отnull, совпадало с шаблоном типаT, еслиvможно было привести кTбез выбрасыванияClassCastException. Этого определения было достаточно, пока роль шаблонов примитивных типов была ограниченной.Теперь, когда шаблоны примитивных типов можно широко использовать, мы обобщаем сопоставление: значение
v, отличное отnull, совпадает с шаблоном типаT, еслиvможно точно привести кT. Так гарантируется, что при сопоставлении с шаблоном примитивного типа информация не теряется.
Исчерпываемость
Выражение switch, а также оператор switch, метки case которого являются шаблонами, должны быть исчерпывающими: все возможные значения выражения-селектора должны обрабатываться в блоке switch. switch является исчерпывающим, если содержит безусловный шаблон типа; он может быть исчерпывающим и по другим причинам, например если охватывает все возможные разрешённые подтипы класса из категории Sealed Classes (запечатанные классы). В некоторых ситуациях switch может считаться исчерпывающим, даже если существуют возможные значения во время выполнения, которые не совпадут ни с одним case; в таких ситуациях компилятор вставляет синтетическую ветвь default для обработки этих непредвиденных входных данных. Исчерпываемость подробнее рассматривается в Patterns: Exhaustiveness, Unconditionality, and Remainder.
С появлением шаблонов примитивных типов мы добавляем одно новое правило определения исчерпываемости: для switch, кандидат на сопоставление которого имеет тип-обёртку W некоторого примитивного типа P, шаблон типа T t исчерпывает W, если T безусловно точен на P; в этом случае null становится частью остатка. Например:
Byte b = ...
switch (b) { // exhaustive switch
case int p -> 0;
}
Здесь кандидат на сопоставление имеет тип-обёртку примитивного типа byte, а преобразование из byte в int безусловно точное. Поэтому switch является исчерпывающим. Это поведение аналогично обработке исчерпываемости в Record Patterns.
Доминирование
Так же как switch использует исчерпываемость, чтобы определить, охватывают ли ветви все входные значения, switch использует доминирование, чтобы определить, есть ли ветви, которые не совпадут ни с одним входным значением. То есть исчерпываемость гарантирует, что никакие возможные входные данные не остаются необработанными (ветвей достаточно), а доминирование гарантирует, что нет недостижимых ветвей (все ветви необходимы).
Один шаблон доминирует над другим, если совпадает со всеми значениями, с которыми совпадает другой шаблон. Например, шаблон типа Object o доминирует над шаблоном типа String s, потому что всё, что совпало бы с String s, совпало бы и с Object o. В switch метка case с неохраняемым шаблоном типа P не может предшествовать метке case с шаблоном типа Q, если P доминирует над Q, потому что Q не с чем было бы совпадать.
Сейчас определение доминирования охватывает только шаблоны ссылочных типов. Мы расширяем его на шаблоны примитивных типов: шаблон типа T t доминирует над шаблоном типа U u, если T t безусловно совпадёт с любым кандидатом типа U. В результате, например, шаблон типа long q доминирует над шаблоном типа int i.
В switch часто одни метки case являются шаблонами типов, например case int i, а другие метки case — константами, например case 42. Так же как мы проверяем метки case, являющиеся шаблонами типов, с помощью доминирования, определяемого безусловной точностью на основе типа, мы можем дополнительно проверять метки case, являющиеся константами, с помощью доминирования, определяемого безусловной точностью на основе значения. Например:
int j = ...;
switch (j) {
case float f -> {}
case 16_777_216 -> {} // error: dominated since 16_777_216 can be
// converted unconditionally exactly to float
default -> {}
}
byte x = ...;
switch (x) {
case short s -> {}
case 42 -> {} // error: dominated since 42 can be
// converted unconditionally exactly to short
}
В switch безусловный шаблон для типа выражения-селектора делает switch исчерпывающим, поскольку совпадает со всеми возможными значениями селектора. Такой шаблон доминирует над всеми следующими за ним метками case, если они есть. Поэтому теперь метка case с безусловным шаблоном не может сопровождаться никакими другими метками case, поскольку над этими метками она доминирует. Например:
int x = ...;
switch (x) {
case int _ -> {} // unconditional pattern
case float _ -> {} // error: dominated
}
Расширенная поддержка примитивных типов в switch
Мы расширяем конструкцию switch, чтобы она охватывала оставшиеся примитивные типы, а именно long, float, double и boolean, а также соответствующие упакованные типы.
Если выражение-селектор имеет тип long, float, double или boolean, все константы в метках case должны иметь тот же тип, что и выражение-селектор, или соответствующий ему упакованный тип. Например, если выражение-селектор имеет тип float или Float, то все константы case должны быть литералами с плавающей точкой (JLS §3.10.2) типа float. Это ограничение необходимо, потому что несоответствие между константами case и выражением-селектором может привести к преобразованиям с потерями, которые противоречат намерениям программиста. Следующий switch допустим, но он был бы недопустим, если бы константа 0f была случайно записана как 0.
float v = ...
switch (v) {
case 0f -> 5f;
case float x when x == 1f -> 6f + x;
case float x -> 7f + x;
}
Семантика литералов с плавающей точкой в метках case определяется через эквивалентность представлений как во время компиляции, так и во время выполнения. Использование двух литералов с плавающей точкой, эквивалентных по представлению, является ошибкой компиляции. Например, следующий switch недопустим, потому что литерал 0.999999999f округляется до 1.0f, и возникает повторяющаяся метка case.
float v = ...
switch (v) {
case 1.0f -> ...
case 0.999999999f -> ... // error: duplicate label
default -> ...
}
Поскольку тип boolean имеет только два различных значения, switch, в котором перечислены обе ветви true и false, считается исчерпывающим. Следующий switch допустим, но он был бы недопустим, если бы в нём была ветвь default.
boolean v = ...
switch (v) {
case true -> ...
case false -> ...
}
Риски и допущения
Более широкое использование безусловности для проверки меток case — это изменение языка, несовместимое на уровне исходного кода: некоторые конструкции switch, которые раньше компилировались, теперь будут вызывать ошибки компиляции. Например:
interface A {}
interface B {}
A a = ...;
switch (a) {
case A _ -> {} // unconditional pattern
case B _ -> {} // error: dominated
}
Первый шаблон не доминирует над вторым, потому что интерфейсы A и B не связаны между собой. Однако тип кандидата на сопоставление, то есть выражения-селектора a, — A, поэтому первый шаблон безусловен и ветвь case B _ никогда не достигается.
Раньше этот switch был допустим, но вводил в заблуждение: сопровождающие код могли ожидать, что ветвь case B _ иногда достигается. (Возможно, в прошлом это ожидание было обоснованным, если, например, у интерфейсов был общий предок, а выражение-селектор было немного другим.) Чтобы не вводить сопровождающих в заблуждение, компиляция этого switch теперь будет приводить к ошибке.
Дальнейшая работа
Упорядочив правила языка, касающиеся сравнения типов и Pattern Matching, мы можем затем рассмотреть введение constant patterns. Сейчас в switch константы могут появляться только как константы case, например 42 в этом коде:
short s = ...
switch (s) {
case 42 -> ...
case int i -> ...
}
Константы не могут появляться в Record Patterns, и это ограничивает полезность Pattern Matching. Например, следующий switch невозможен:
record Box(short s) {}
Box b = ...
switch (b) {
case Box(42) -> ... // Box(42) is not a valid record pattern
case Box(int i) -> ...
}
Благодаря определённым здесь правилам применимости появление констант в Record Patterns можно было бы разрешить. Проверка представления, благодаря которой сегодняшние константы case проходят проверку типов, обобщается описанным выше механизмом точности, и он закладывает основу для дальнейшей работы над constant patterns.