This document describes changes to the Java Language
Specification to support primitive types to pattern matching of
instanceof
, which is a preview feature of Java SE 23. See
JEP:455 for overview of the
feature.
Changes are described with respect to existing sections of the JLS.
New text is indicated like this and deleted text is
indicated like this. Explanation and discussion, as needed,
is set aside in grey boxes.
Changelog:
2024-04-24: Small corrections around determination for the exactly promoted type.
2024-03-28: Add Example 5.7.1-1. Exact Testing Conversion of Primitive Types and update links to point to JDK 22.
2024-02-22: Small correction around applicability.
2024-01-22: Small correction of equality checks in exactness.
2023-12-13: Streamlining of exact testing conversions in 5.7.1 and 5.7.2.
2023-12-11: Update on top of JLS 22 (inclusion of unnamed variables & patterns, and testing contexts in 5.7).
2023-12-07: Introduce exactly promoted type.
2023-11-17: Use a wider format W for the exactness definition.
2023-10-30: Simplifications: exactness decoupled from unconditional exactness, instanceof as a type comparison operator streamlined, property of unconditional.
2023-10-10: Editorial changes and added JEP number.
2023-09-13: Update draft spec on top of JLS 21. Move exactness under casting context in 5.5.
2023-01-25: First draft. Add definition of exact representation conversions in 5.1.13. Update section about instanceof, pattern properties and switch.
Chapter 1: Introduction
1.4 Relationship to Predefined Classes and Interfaces
As noted above, this specification often refers to classes and
interfaces of the Java SE Platform API. In particular, some classes and
interfaces have a special relationship with the Java programming
language. Examples include classes such as Object
,
Class
, ClassLoader
, String
, and
Thread
,; the class
java.math.BigDecimal
; the interface
java.io.Serializable
; and the classes and
interfaces in the package java.lang.reflect
, among others.
This specification constrains the behavior of such classes and
interfaces, but does not provide a complete specification for them. The
reader is referred to the Java SE Platform API documentation.
Consequently, this specification does not describe reflection in any
detail. Many linguistic constructs have analogs in the Core Reflection
API (java.lang.reflect
) and the Language Model API
(javax.lang.model
), but these are generally not discussed
here. For example, when we list the ways in which an object can be
created, we generally do not include the ways in which the Core
Reflection API can accomplish this. Readers should be aware of these
additional mechanisms even though they are not mentioned in the
text.
Chapter 5: Conversions and Contexts
Every expression written in the Java programming language either produces no result (15.1) or has a type that can be deduced at compile time (15.3). When an expression appears in most contexts, it must be compatible with a type expected in that context; this type is called the target type. For convenience, compatibility of an expression with its surrounding context is facilitated in two ways:
First, for some expressions, termed poly expressions (15.2), the deduced type can be influenced by the target type. The same expression can have different types in different contexts.
Second, after the type of the expression has been deduced, an implicit conversion from the type of the expression to the target type can sometimes be performed.
If neither strategy is able to produce the appropriate type, a compile-time error occurs.
The rules determining whether an expression is a poly expression, and if so, its type and compatibility in a particular context, vary depending on the kind of context and the form of the expression. In addition to influencing the type of the expression, the target type may in some cases influence the run time behavior of the expression in order to produce a value of the appropriate type.
Similarly, the rules determining whether a target type allows an implicit conversion vary depending on the kind of context, the type of the expression, and, in one special case, the value of a constant expression (15.29). A conversion from type S to type T allows an expression of type S to be treated at compile time as if it had type T instead. In some cases this will require a corresponding action at run time to check the validity of the conversion or to translate the run-time value of the expression into a form appropriate for the new type T.
Example 5.0-1. Conversions at Compile Time and Run Time
A conversion from type
Object
to typeThread
requires a run-time check to make sure that the run-time value is actually an instance of classThread
or one of its subclasses; if it is not, an exception is thrown.A conversion from type
Thread
to typeObject
requires no run-time action;Thread
is a subclass ofObject
, so any reference produced by an expression of typeThread
is a valid reference value of typeObject
.A conversion from type
int
to typelong
requires run-time sign-extension of a 32-bit integer value to the 64-bitlong
representation. No information is lost.A conversion from type
double
to typelong
requires a non-trivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost.
The conversions possible in the Java programming language are grouped into several broad categories:
- Identity conversions
- Widening primitive conversions
- Narrowing primitive conversions
- Widening reference conversions
- Narrowing reference conversions
- Boxing conversions
- Unboxing conversions
- Unchecked conversions
- Capture conversions
- String conversions
There are seven kinds of conversion contexts in which poly expressions may be influenced by context or implicit conversions may occur. Each kind of context has different rules for poly expression typing and allows conversions in some of the categories above but not others. The contexts are:
Assignment contexts (5.2, 15.26), in which an expression's value is bound to a named variable. Primitive and reference types are subject to widening, values may be boxed or unboxed, and some primitive constant expressions may be subject to narrowing. An unchecked conversion may also occur.
Strict invocation contexts (5.3, 15.9, 15.12), in which an argument is bound to a formal parameter of a constructor or method. Widening primitive, widening reference, and unchecked conversions may occur.
Loose invocation contexts (5.3, 15.9, 15.12), in which, like strict invocation contexts, an argument is bound to a formal parameter. Method or constructor invocations may provide this context if no applicable declaration can be found using only strict invocation contexts. In addition to widening and unchecked conversions, this context allows boxing and unboxing conversions to occur.
String contexts (5.4, 15.18.1), in which a value of any type is converted to an object of type
String
.Casting contexts (5.5), in which an expression's value is converted to a type explicitly specified by a cast operator (15.16). Casting contexts are more inclusive than assignment or loose invocation contexts, allowing any specific conversion other than a string conversion, but certain casts to a reference type are checked for correctness at run time.
Numeric contexts (5.6), in which the operands of a numeric operator or some other expressions that operate on numbers may be widened to a common type.
Testing contexts (5.7), in which an expression's value is compared and possibly converted to a type explicitly specified by the type comparison operator (15.20.2) or
a pattern (14.30)pattern matching (14.30.2). Testing contexts are more inclusive than assignment or loose invocation contexts, but not as inclusive as casting contexts.
The term "conversion" is also used to describe, without being specific, any conversions allowed in a particular context. For example, we say that an expression that is the initializer of a local variable is subject to "assignment conversion", meaning that a specific conversion will be implicitly chosen for that expression according to the rules for the assignment context. As another example, we say that an expression undergoes "casting conversion" to mean that the expression's type will be converted as permitted in a casting context.
Example 5.0-2. Conversions In Various Contexts
class Test {
public static void main(String[] args) {
// Casting conversion (5.5) of a float literal to
// type int. Without the cast operator, this would
// be a compile-time error, because this is a
// narrowing conversion (5.1.3):
int i = (int)12.5f;
// String conversion (5.4) of i's int value:
System.out.println("(int)12.5f==" + i);
// Assignment conversion (5.2) of i's value to type
// float. This is a widening conversion (5.1.2):
float f = i;
// String conversion of f's float value:
System.out.println("after float widening: " + f);
// Numeric promotion (5.6) of i's value to type
// float. This is a binary numeric promotion.
// After promotion, the operation is float*float:
System.out.print(f);
f = f * i;
// Two string conversions of i and f:
System.out.println("*" + i + "==" + f);
// Invocation conversion (5.3) of f's value
// to type double, needed because the method Math.sin
// accepts only a double argument:
double d = Math.sin(f);
// Two string conversions of f and d:
System.out.println("Math.sin(" + f + ")==" + d);
}
}
This program produces the output:
(int)12.5f==12
after float widening: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934
5.1 Kinds of Conversion
5.1.2 Widening Primitive Conversion
19 specific conversions on primitive types are called the widening primitive conversions:
byte
toshort
,int
,long
,float
, ordouble
short
toint
,long
,float
, ordouble
char
toint
,long
,float
, ordouble
int
tolong
,float
, ordouble
long
tofloat
ordouble
float
todouble
A widening primitive conversion that does not lose
information about the overall magnitude of a numeric value in the
following cases, where the numeric value is preserved
exactlyis called an exact widening primitive
conversion and the numeric value is preserved exactly. Such a
conversion can be one of the following:
from an integral type to another integral type
from
byte
,short
, orchar
to a floating-point typefrom
int
todouble
from
float
todouble
A widening primitive conversion from int
to
float
, or from long
to float
, or
from long
to double
, may result in loss of
precision, that is, the result may lose some of the least
significant bits of the value. In this case, the resulting
floating-point value will be a correctly rounded version of the integer
value, using the round to nearest rounding policy (15.4).
A widening conversion of a signed integer value to an integral type T simply sign-extends the two's-complement representation of the integer value to fill the wider format.
A widening conversion of a char
to an integral type
T zero-extends the representation of the char
value to fill the wider format.
A widening conversion from int
to float
, or
from long
to float
, or from int
to double
, or from long
to double
occurs as determined by the rules of IEEE 754 for converting from an
integer format to a binary floating-point format.
A widening conversion from float
to double
occurs as determined by the rules of IEEE 754 for converting between
binary floating-point formats.
Despite the fact that loss of precision may occur, a widening primitive conversion never results in a run-time exception (11.1.1).
Example 5.1.2-1. Widening Primitive Conversion
class Test {
public static void main(String[] args) {
int big = 1234567890;
float approx = big;
System.out.println(big - (int)approx);
}
}
This program prints:
-46
thus indicating that information was lost during the conversion from
type int
to type float
because values of type
float
are not precise to nine significant digits.
5.1.3 Narrowing Primitive Conversion
22 specific conversions on primitive types are called the narrowing primitive conversions:
short
tobyte
orchar
char
tobyte
orshort
int
tobyte
,short
, orchar
long
tobyte
,short
,char
, orint
float
tobyte
,short
,char
,int
, orlong
double
tobyte
,short
,char
,int
,long
, orfloat
A narrowing primitive conversion may lose information about the overall magnitude of a numeric value, and may also lose precision and range.
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T. In addition to a possible loss of information about the magnitude of the numeric value, this may cause the sign of the resulting value to differ from the sign of the input value.
A narrowing conversion of a char
to an integral type
T likewise simply discards all but the n lowest order
bits, where n is the number of bits used to represent type
T. In addition to a possible loss of information about the
magnitude of the numeric value, this may cause the resulting value to be
a negative number, even though chars represent 16-bit unsigned integer
values.
A narrowing conversion of a floating-point number to an integral type T takes two steps:
In the first step, the floating-point number is converted either to a
long
, if T islong
, or to anint
, if T isbyte
,short
,char
, orint
, as follows:If the floating-point number is NaN (4.2.3), the result of the first step of the conversion is an
int
orlong
0
.Otherwise, if the floating-point number is not an infinity, the floating-point value is rounded to an integer value V using the round toward zero rounding policy (4.2.4). Then there are two cases:
If T is
long
, and this integer value can be represented as along
, then the result of the first step is thelong
value V.Otherwise, if this integer value can be represented as an
int
, then the result of the first step is theint
value V.
Otherwise, one of the following two cases must be true:
The value must be too small (a negative value of large magnitude or negative infinity), and the result of the first step is the smallest representable value of type
int
orlong
.The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type
int
orlong
.
In the second step:
If T is
int
orlong
, the result of the conversion is the result of the first step.If T is
byte
,char
, orshort
, the result of the conversion is the result of a narrowing conversion to type T (5.1.3) of the result of the first step.
A narrowing conversion from double
to float
occurs as determined by the rules of IEEE 754 for converting between
binary floating-point formats, using the round to nearest rounding
policy (15.4).
This conversion can lose precision, but also lose range, resulting in a
float
zero from a nonzero double
and a
float
infinity from a finite double
. A
double
NaN is converted to a float
NaN and a
double
infinity is converted to the same-signed
float
infinity.
There are several pairs of types that are represented using the same number of bits:
char
andshort
,int
andfloat
, andlong
anddouble
. Converting from one type to another where they are both represented by the same number of bits may lose information because the bits are used differently by different types. For example,char
andshort
are both integral types represented using 16 bits. Because conversions in both directions can lose information, a narrowing conversion is provided both forchar
toshort
andshort
tochar
. Becausechar
is unsigned andshort
is signed, narrowingchar
toshort
orshort
tochar
may lose magnitude. ConvertingCharacter.MAX_VALUE
(65535) toshort
gives -1 ((216 - 1) - 216). Converting ashort
value of -1 tochar
gives 65535 (216 - 1). Because everyint
value can be converted to afloat
value of closely similar magnitude (the resulting numeric value after the conversion may lose only some of the least significant bits of the value), we classify the conversion fromint
tofloat
as widening (for example converting theint
123456789 tofloat
rounds up to 123456792, losing precision). A narrowing conversion is provided fromfloat
toint
, because largerfloat
values can lose most of their magnitude, when approximated asint
values (for example converting theFloat.MAX_VALUE
toint
results in 2147483647, a significantly smaller numerical value in magnitude).
Despite the fact that overflow, underflow, or other loss of information may occur, a narrowing primitive conversion never results in a run-time exception (11.1.1).
Example 5.1.3-1. Narrowing Primitive Conversion
class Test {
public static void main(String[] args) {
float fmin = Float.NEGATIVE_INFINITY;
float fmax = Float.POSITIVE_INFINITY;
System.out.println("long: " + (long)fmin +
".." + (long)fmax);
System.out.println("int: " + (int)fmin +
".." + (int)fmax);
System.out.println("short: " + (short)fmin +
".." + (short)fmax);
System.out.println("char: " + (int)(char)fmin +
".." + (int)(char)fmax);
System.out.println("byte: " + (byte)fmin +
".." + (byte)fmax);
}
}
This program produces the output:
long: -9223372036854775808..9223372036854775807
int: -2147483648..2147483647
short: 0..-1
char: 0..65535
byte: 0..-1
The results for char
, int
, and
long
are unsurprising, producing the minimum and maximum
representable values of the type.
The results for byte
and short
lose
information about the sign and magnitude of the numeric values and also
lose precision. The results can be understood by examining the low order
bits of the minimum and maximum int
. The minimum
int
is, in hexadecimal, 0x80000000
, and the
maximum int is 0x7fffffff
. This explains the
short
results, which are the low 16 bits of these values,
namely, 0x0000
and 0xffff
; it explains the
char results, which also are the low 16 bits of these values, namely,
'\u0000'
and '\uffff'
; and it explains the
byte results, which are the low 8 bits of these values, namely,
0x00
and 0xff
.
Example 5.1.3-2. Narrowing Primitive Conversions that lose information
class Test {
public static void main(String[] args) {
// A narrowing of int to short loses high bits:
System.out.println("(short)0x12345678==0x" +
Integer.toHexString((short)0x12345678));
// An int value too big for byte changes sign and magnitude:
System.out.println("(byte)255==" + (byte)255);
// A float value too big to fit gives largest int value:
System.out.println("(int)1e20f==" + (int)1e20f);
// A NaN converted to int yields zero:
System.out.println("(int)NaN==" + (int)Float.NaN);
// A double value too large for float yields infinity:
System.out.println("(float)-1e100==" + (float)-1e100);
// A double value too small for float underflows to zero:
System.out.println("(float)1e-50==" + (float)1e-50);
}
}
This program produces the output:
(short)0x12345678==0x5678
(byte)255==-1
(int)1e20f==2147483647
(int)NaN==0
(float)-1e100==-Infinity
(float)1e-50==0.0
5.5 Casting Contexts
Casting contexts allow the operand of a cast expression (15.16) to be converted to the type explicitly named by the cast operator. Compared to assignment contexts and invocation contexts, casting contexts allow the use of more of the conversions defined in 5.1, and allow more combinations of those conversions.
If the expression is of a primitive type, then a casting context allows the use of one of the following:
an identity conversion (5.1.1)
a widening primitive conversion (5.1.2)
a narrowing primitive conversion (5.1.3)
a widening and narrowing primitive conversion (5.1.4)
a boxing conversion (5.1.7)
a boxing conversion followed by a widening reference conversion (5.1.5)
If the expression is of a reference type, then a casting context allows the use of one of the following:
an identity conversion (5.1.1)
a widening reference conversion (5.1.5)
a widening reference conversion followed by an unboxing conversion
a widening reference conversion followed by an unboxing conversion, then followed by a widening primitive conversion
a narrowing reference conversion (5.1.6)
a narrowing reference conversion followed by an unboxing conversion
an unboxing conversion (5.1.8)
an unboxing conversion followed by a widening primitive conversion
If the expression has the null type, then the expression may be cast to any reference type.
If a casting context makes use of a narrowing reference conversion
that is checked or partially unchecked (5.1.6.2,
5.1.6.3),
then a run time check will be performedthe conversion
performs a validity check at run time on the class of the
expression's value, possibly causing a ClassCastException
.
Otherwise, no run time check is performedno validity
check is performed at run time, although other actions may be performed
at run time.
5.7 Testing Contexts
Testing contexts arise for expressions when a value of one
type is to be compared and possibly converted to another type.
Testing contexts allow the operand of a type comparison operator
(15.20.2) to be compared to another
type. Testing contexts also allow the operand
of a pattern match operator (15.20.2), or the
selector expression of a switch
expression or statement
that has at least one pattern case
label associated with
its switch block (14.11.1) to be
compared and converted to a type as part of the process
of pattern matching. As pattern matching is an inherently conditional
process (14.30.2), it is expected that a
testing context will make use of conversions that may fail or
lose information at run time.
Testing contexts use similar conversions for reference
types as casting contexts except that they do not permit narrowing
reference conversions that are unchecked (5.1.6.2).
If the expression is of a primitive type, then a testing context
allows the use of an identity conversion (5.1.1).one
of the following:
If the expression is of a reference type, then a testing context allows the use of one of the following:
a widening reference conversion followed by an unboxing conversion
a widening reference conversion followed by an unboxing conversion, then followed by a widening primitive conversion
- a narrowing reference conversion that is checked (5.1.6.2)
a narrowing reference conversion that is checked followed by an unboxing conversion
an unboxing conversion (5.1.8)
an unboxing conversion followed by a widening primitive conversion
If the expression has the null type, then the expression may be converted to any reference type.
If a testing context makes use of a narrowing reference conversion,
then a run time check will be performed on the class of the expression's
value, possibly causing a ClassCastException
.
Whether there is a testing conversion from type S to type
T is distinct from whether a value of type S can be
converted to type T without loss of information. For example,
there is a testing conversion from int
to
byte
, and from Object
to String
,
but there are many int
values that cannot be represented as
a byte
, and there are many Object
values that
do not refer to instances of String
. The run-time process
of pattern matching is sensitive to whether loss of information occurs,
so it relies on the notion of a testing conversion being exact for a
given value.
5.7.1 Exact Testing Conversions
A testing conversion of a value is exact if it yields a result without loss of information or throwing an exception. Otherwise, it is inexact.
Loss of information can occur during either a widening primitive conversion that is not exact (5.1.2), or a narrowing primitive conversion (5.1.3), or a widening and narrowing primitive conversion (5.1.4). The loss can take one or more of the following forms:
- loss of magnitude (a primitive value's distance from zero)
- loss of precision (a primitive value's least significant bits)
- loss of range (a primitive value's
MIN_VALUE
andMAX_VALUE
) - loss of sign (a primitive value appears with a flipped sign after the conversion, or no sign at all)
Applying one of these conversions may lead to loss of information that in turn is a potential source of bugs. For example, if the
int
variablei
stores the value1000
then a narrowing primitive conversion tobyte
will yield the result-24
. Loss of information has occurred: both the magnitude and the sign of the result are different than those of the original value. As such, a conversion fromint
tobyte
for the value1000
is inexact. In contrast, a conversion fromint
tobyte
for the value10
is exact because the result,10
, is the same as the original value.
An exception can occur during either a narrowing reference conversion that is checked (5.1.6.2), or an unboxing conversion (5.1.8).
A run time check is needed to determine whether a conversion causes loss of information or throws an exception. If the check determines that no loss of information occurs, or no exception is thrown, then the conversion is exact. Otherwise the conversion is inexact, and its result or exception is discarded as if the conversion had never occurred.
If a testing conversion consists of more than one conversion, then if all such conversions are exact then the testing conversion is exact; otherwise the testing conversion is inexact.
The run time check to determine whether the conversion of a value from a primitive type S to a primitive type T loses information is non-obvious. The obvious way to check for loss of information would be to convert the value from S to T, then convert the result from T back to S, then compare that final result with the original value. However, this "round trip" conversion from S to T and back to S would be misleading because it would be possible for the final result to equal the original value even if the original conversion of the value from S to T was inexact.
As an example, consider the conversion of
Integer.MAX_VALUE
fromint
tofloat
, with resulty
. This conversion is inexact due to the round to nearest rounding policy: loss of precision occurs (15.4). Convertingy
back toint
, with resultz
, is also inexact due to the round toward zero rounding policy (4.2.4). In this case,z
is equal to the original valueInteger.MAX_VALUE
, but it would be erroneous to conclude that the original conversion ofInteger.MAX_VALUE
fromint
tofloat
preserved all information.
As another example, consider the conversion of
Character.MAX_VALUE
fromchar
toshort
. This conversion narrows an unsigned 16-bit value, 65535, to a signed 16-bit value, -1. The conversion is inexact because loss of magnitude occurs: thechar
value is 65535 integral values away from zero while theshort
value is only one integral value away from zero. Converting theshort
value back tochar
is also inexact. It narrows a signed 16-bit value, -1, to an unsigned 16-bit value, 65535, so loss of magnitude occurs again. Even though 65535 is equal to the original valueCharacter.MAX_VALUE
, it would be erroneous to conclude that the original conversion ofCharacter.MAX_VALUE
fromchar
toshort
preserved all information.
The safe but non-obvious way to check for loss of information is to convert the value from S to T, then promote both the result and the original value to a type capable of representing all the values of S and T. This type is known as the exactly promoted type. If the original conversion from S to T lost information then the two promoted values will compare as unequal, revealing the loss of information. There is no possibility of the original conversion's inexactness being cancelled out by the inexactness of another conversion, as can happen with a "round trip".
For a testing conversion from a primitive type S to a primitive type T, the exactly promoted type is determined as follows:
If S and T are the same primitive type, then the exactly promoted type is S.
If S is
byte
orshort
and T ischar
, or vice versa, then the exactly promoted type isint
.If S is
int
and T isfloat
, or vice versa, then the exactly promoted type isdouble
.If S is
long
and T isfloat
ordouble
, or vice versa, then the exactly promoted type is java.math.BigDecimal.Otherwise, the exactly promoted type is the wider of S and T. A type A is wider than a type B if there is a widening primitive conversion from B to A (5.1.2).
Let C be a testing conversion of a value v from a primitive type S to a primitive type T. Then one of the following holds:
If S and T are both floating-point types, and v is either a zero, or an infinity, or a
NaN
, then C is exact.If S is a floating-point type, T is an integral type, and v is either negative zero, or an infinity, or a
NaN
, then C is inexact.For example, if v is a floating-point negative zero that is converted to an integral type, then the result is zero. Relative to the original value, the result has lost information: the sign.
Otherwise, let P denote the exactly promoted type of S and T, and let w denote the result of the conversion C. Then:
Let x denote the result of the conversion D of the value v from S to P, computed as follows:
If P is a primitive type, x is the result of a casting conversion from S to P.
If P is java.math.BigDecimal, x is the result obtained as if by execution of
new BigDecimal(v)
.
Let y denote the result of the conversion E of the value w from T to P, computed as follows:
If P is a primitive type, y is the result of a casting conversion from T to P.
If P is java.math.BigDecimal, y is the result obtained as if by execution of
new BigDecimal(w)
.
C is exact if one of the following holds:
P is an integral type and x and y are equal according to the numerical equality operator
==
(15.21.1).P is a floating-point type and x and y are equal according to the result obtained as if by execution of
Double.compare(x, y) == 0
(java.lang.Double.compare).P is java.math.BigDecimal and x and y are equal according to the result obtained as if by execution of
x.compareTo(y) == 0
(java.math.BigDecimal.compareTo).
Otherwise, C is inexact.
The following diagram gives an overview of the conversions C, D and E:
x -- equal? -- y (P) (P) | | ^ ^ D E ^ ^ | | v ---- C ----> w (S) (T)
Example 5.7.1-1. Exact Testing Conversion of Primitive Types
Consider the conversion from the source type byte
to the
target type char
. The exactly promoted type of these two
types is int
. The run time check to determine whether the
conversion of a byte
value x
to a
char
is exact, is given by the following equation:
(int)(char) x == (int) x
The rationale of using the exactly promoted type and not a round-trip
cast to check for loss of information is, as discussed above, that a
round-trip cast does not always reveal the potential loss of
information. For example, using the round-trip cast to determine whether
the conversion of the byte
value x
to a
char
is exact leads to the following equation:
(byte)(char) x == x
For the following maximum allowed value of x
, the
round-trip cast would evaluate to true
:
byte x = 127
(byte)(char) x == x // evaluates to true
For the following minimum allowed value of x
, the
round-trip cast would also evalue to true, erroneously:
byte x = -128
(byte)(char) x == x // also evaluates to true
The first cast conversion (char) x
evaluates to 'タ',
which is the character whose representation is 65408
. And
when 65408
is cast back, via a second cast conversion
to byte
, the result is -128
again and the
equality operator evaluates to true
:
(byte)(char) x // evaluates to -128
However, the numerical value of -128
is not the same
with the numerical value of 65408
but due to modulo
arithmetic the result of the second conversion is the same number as the
original value of x
. As an unfortunate result of this, a
user would erroneously deduce that the original value was preserved and
that the conversion of x
to char
would be
safe; the user would have lost the original information applying this
conversion.
For that reason, the exactly promoted type is used so that a
conversion of both the original value x
and of the
converted value (char) x
to the exactly promoted type
int
(a wider type), reveals, correctly, that the two values
are different. Thus, such a conversion would be inexact.
5.7.2 Unconditionally Exact Testing Conversions
Most conversions allowed in a testing context are exact or inexact depending on the value that is converted at run time. However, some conversions are always exact regardless of the value. These conversions are said to be unconditionally exact. It is known at compile time that an unconditionally exact conversion will yield a result at run time without loss of information or throwing an exception. The unconditionally exact conversions are:
an identity conversion
an exact widening primitive conversion
a widening reference conversion
a boxing conversion
a boxing conversion followed by a widening reference conversion
For example, a widening primitive conversion from
byte
toint
is unconditionally exact because it will always succeed with no loss of information about the magnitude of the numeric value.
Some of these conversions require action at run time, and in the case of a boxing conversion (5.1.7) may even fail with an
OutOfMemoryError
. However, the run-time behavior does not affect whether the conversion is unconditionally exact.
Unconditional exactness is used at compile time to check the dominance of one pattern over another in a switch block (14.11.1), and whether a switch block as a whole is exhaustive (14.11.1.1).
Chapter 14: Blocks, Statements, and Patterns
14.11 The switch
Statement
The switch
statement transfers control to one of several
statements or expressions, depending on the value of an expression.
- SwitchStatement:
-
switch
(
Expression)
SwitchBlock
The Expression is called the selector expression.
The type of the selector expression must be may be any
type.char
,
byte
, short
, int
, or a reference
type, or a compile-time error occurs
The types allowed for a selector expression have been expanded over the years. Java SE 1.0 supported
byte
,short
,char
,int
selector types. In Java SE 5.0 autoboxing was introduced, so switch was expanded to support the wrapper classesByte
,Short
,Character
,Integer
, alongside enumerations. In Java SE 7,String
was added. In Java SE 21, reference types were added (preserving the existing support for primitives) to account for pattern matching with type patterns in case labels. Java SE 23 supports all primitive types and their boxes, extending support withcase
constants to allow longs, floats, etc.; lifting all restrictions on the values that can be compared and pattern matched viaswitch
andinstanceof
(15.20.2) allows performing data exploration uniformly.
14.11.1 Switch Blocks
The body of both a switch
statement and a
switch
expression (15.28)
is called a switch block. This subsection presents general
rules which apply to all switch blocks, whether they appear in
switch
statements or switch
expressions. Other
subsections present additional rules which apply either to switch blocks
in switch
statements (14.11.2)
or to switch blocks in switch
expressions (15.28.1).
- SwitchBlock:
-
{
SwitchRule {SwitchRule}}
-
{
{SwitchBlockStatementGroup} {SwitchLabel:
}}
- SwitchRule:
-
SwitchLabel
->
Expression;
-
SwitchLabel
->
Block -
SwitchLabel
->
ThrowStatement - SwitchBlockStatementGroup:
-
SwitchLabel
:
{SwitchLabel:
} BlockStatements - SwitchLabel:
-
case
CaseConstant {,
CaseConstant} -
case
null
[,
default
] -
case
CasePattern {,
CasePattern} [Guard] -
default
- CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern
- Guard:
-
when
Expression
A switch block can consist of either:
Switch rules, which use
->
to introduce either a switch rule expression, a switch rule block, or a switch rulethrow
statement; orSwitch labeled statement groups, which use
:
to introduce switch labeled block statements.
Every switch rule and switch labeled statement group starts with a
switch label, which is either a case
label or a
default
label. Multiple switch labels are permitted for a
switch labeled statement group.
A case
label has either a (non-empty) list of
case
constants, a null
literal, or a
(non-empty) list of case
patterns.
Every case
constant must be either a constant expression
(15.29),
or the name of an enum constant (8.9.1),
otherwise a compile-time error occurs.
A case
label with a null
literal may have
an optional default
.
A case
label with case
patterns may have an
optional when
expression, known as a guard, which
represents a further test on values that match the patterns. A
case
label is said to be unguarded if either (i)
it has no guard, or (ii) it has a guard that is a constant expression
(15.29)
with value true
; and guarded otherwise.
It is a compile-time error for a case
label to have more
than one case
pattern and declare any pattern variables
(other than those declared by a guard associated with the
case
label).
If a
case
label with more than onecase
pattern could declare pattern variables, then it would not be clear which variables would be initialized if thecase
label were to apply. For example:
Object obj = ...; switch (obj) { case Integer i, Boolean b -> { ... // Error! Is i or b initialized? } ... }
Even if only one of the
case
patterns declares a pattern variable, it would still not be clear whether the variable was initialized or not; for example:
Object obj = ...; switch (obj) { case Integer i, Boolean _ -> { ... // Error! Is i initialized? } ... }
The following does not result in a compile-time error:
Object obj = ...; switch (obj) { case Integer _, Boolean _ -> { ... // Matches both an Integer and a Boolean } ... }
Switch labels and their case
constants,
null
literals, and case
patterns are said to
be associated with the switch block.
For a given switch block bothall of the
following must be true, otherwise a compile-time error occurs:
No two of the
case
constants associated with a switch block may have the same value.No more than one
null
literal may be associated with a switch block.No more than one
default
label may be associated with a switch block.
A guard associated with a case
label must satisfy all of
the following conditions, otherwise a compile-time error occurs:
A guard must have type
boolean
orBoolean
.Any local variable, formal parameter, or exception parameter used but not declared in a guard must either be
final
or effectively final (4.12.4).Any blank
final
variable used but not declared in a guard must be definitely assigned (16) before the guard.A guard cannot be a constant expression (15.29) with the value
false
.
The switch block of a switch
statement or a
switch
expression is switch compatible with the
type of the selector expression, T, if all of the following are
true:
If a
null
literal is associated with the switch block, then T is a reference type.For every
case
constant associated with the switch block that names an enum constant, the type of thecase
constant is assignment compatible with T (5.2).
- For each
case
constant associated with the switch block that is a constant expression, the constant is assignment compatible with T, and T is one ofchar
,byte
,short
,int
,Character
,Byte
,Short
,Integer
, orString
.
For each
case
constant associated with the switch block that is a constant expression, one of the following is true:if T is one of
char
,byte
,short
,int
,Character
,Byte
,Short
,Integer
, orString
, the constant is assignment compatible with T.if T is one of
long
,float
,double
, orboolean
, the type of the case constant is T.if T is one of
Long
,Float
,Double
, orBoolean
, the type of the case constant is, respectively,long
,float
,double
, orboolean
.
Note that
case
constants must be assignment compatible with the selector expression, whilecase
patterns allow values to be compared and converted in a testing context (5.7, 14.30.3).
Prior to Java SE 23,
case
constants were limited to the types given in the first item:char
,byte
,short
, etc. Java SE 23 expanded the set of validcase
constants to allow longs, floats, etc.
Note that a selector expression of type
long
,float
, ordouble
requirescase
constants that are, respectively, integer literals of typelong
(5L
), floating-point literals of typefloat
(5f
), and floating-point literals of typedouble
(5d
). Intermixing floating-point selector types with integral literals is not allowed. For example a widening primitive conversion fromint
tofloat
is lossy. While, a widening primitive conversion fromint
todouble
is unconditionally exact, integer literals are disallowed since mixing implicit and explicit conversions incase
literals could result in incorrect code in case of complex constant expressions.
- Every pattern p associated with the switch block is applicable at type T (14.30.3).
Switch blocks are not designed to work with the types
boolean
,long
,float
, anddouble
. The selector expression of aswitch
statement orswitch
expression can not have one of these types.
The switch block of a switch
statement or a
switch
expression must be switch compatible with the type
of the selector expression, or a compile-time error occurs.
If the switch block of a switch
statement or
switch
expression whose selector expression is of type
boolean
or Boolean
has one case
constant associated with the switch block that names true
,
one that names false
, and a default
label,
then a compile-time error occurs.
A switch label in a switch block is said to be dominated if for every value that it applies to, it can be determined that one of the preceding switch labels would also apply. It is a compile-time error if any switch label in a switch block is dominated. The rules for determining whether a switch label is dominated are as follows:
A
case
label with acase
pattern q is dominated if there is a preceding unguardedcase
label in the switch block with acase
pattern p, and p dominates q (14.30.3).The definition of one pattern dominating another pattern is based on types. For example, the type pattern
Object
o
dominates the type patternString
s
, and so the following results in a compile-time error:Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error! System.out.println("A string"); }
A guarded
case
label with acase
pattern is dominated by acase
label with the same pattern but without the guard. For example, the following results in a compile-time error:String str = ...; switch (str) { case String s -> System.out.println("A string"); case String s when s.length() == 2 -> // Error! System.out.println("Two character string"); ... }
On the other hand, a guarded
case
label with acase
pattern is not considered to dominate an unguardedcase
label with the samecase
pattern. This allows the following common pattern programming style:Integer j = ...; switch (j) { case Integer i when i <= 0 -> System.out.println("Less than or equal to zero"); case Integer i -> System.out.println("An integer"); }
The only exception is where the guard is a constant expression that has the value
true
, for example:Integer j = ...; switch (j) { case Integer i when true -> // Ok System.out.println("An integer"); case Integer i -> // Error! System.out.println("An integer"); }
A
case
label with more than onecase
pattern is dominated if any one of these patterns is dominated by a pattern that appears as acase
pattern in a preceding unguardedcase
label, and so the following results in a compile-time error (as the type patternInteger
_
is dominated by the type patternNumber
_
):Object obj = ... switch (obj) { case Number _ -> System.out.println("A Number"); case Integer _, String _ -> // Error - dominated! System.out.println("An Integer or a String"); ... }
A
case
label with acase
constant c is dominated if one of the following holds:c is a constant expression of a primitive type S, and there is a preceding
case
label in the switch block with an unguardedcase
pattern p, where p is unconditional (14.30.3) for the wrapper class of S.c is a constant expression of a reference type T, and there is a preceding
case
label in the switch block with an unguardedcase
pattern p, where p is unconditional for the type T.c names an enum constant of enum class E, and there is a preceding
case
label in the switch block with an unguardedcase
pattern p, where p is unconditional for the type E.
For example, a
case
label with anInteger
type pattern dominates acase
label with an integer literal:Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); }
A
default
label or acase
null,
default
label is dominated if there is a preceding unguardedcase
label in the switch block with acase
pattern p where p is unconditional for the type of the selector expression(14.30.3).A
case
label with acase
pattern that is unconditional for the type of the selector expression will, as the name suggests, match every value and so behave like adefault
label. A switch block can not have more than one switch label that acts like adefault
.
It is a compile-time error if there is a case
label with
n (n>1) case
patterns
p1, ..., pn in a switch block
where one of the patterns pi
(1≤i<n) dominates another of the patterns
pj (i<j≤n).
It is a compile-time error if any of the following holds:
There is a
default
label in the switch block that precedes acase
label withcase
patterns.There is a
default
label in the switch block that precedes acase
label with anull
literal.There is a
case
null,
default
label in the switch block followed by any other switch label.
If used, a
default
label should come last in a switch block.
For compatibility reasons, a
default
label may appear beforecase
labels that do not have anull
literal orcase
patterns.
int i = ...; switch(i) { default -> System.out.println("Some other integer"); case 42 -> // allowed System.out.println("42"); }
If used, a
case
null,
default
label should come last in a switch block.
It is a compile-time error if, in a switch block that consists of
switch labeled statement groups, a statement is labeled with a
case
label that declares one or more pattern variables
([6.3.3]), and either:
An immediately preceding statement in the switch block can complete normally (14.22), or
The statement is labeled with more than one switch label.
The first condition prevents a statement group from "falling through" to another statement group without initializing pattern variables. For example, were the statement labeled by
case
Integer
i
reachable from the preceding statement group, the pattern variablei
would not have been initialized:
Object o = "Hello"; switch (o) { case String s: System.out.println("String: " + s ); // No break! case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: }
Switch blocks consisting of switch label statement groups allow multiple labels to apply to a statement group. The second condition prevents a statement group from being executed based on one label without initializing the pattern variables of another label. For example:
Object o = "Hello World"; switch (o) { case String s: case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: } Object obj = null; switch (obj) { case null: case String s: System.out.println(s); // Error! Can be reached // without matching the // pattern `String s` default: }
Both of these conditions apply only when the
case
pattern declares pattern variables. The following examples, in contrast, are unproblematic:
record R() {} record S() {} Object o = "Hello World"; switch (o) { case String s: System.out.println(s); // No break case R(): // No pattern variables declared System.out.println("It's either an R or a string"); break; default: } Object ob = new R(); switch (ob) { case R(): case S(): // Multiple case labels System.out.println("Either R or an S"); break; default: } Object obj = null; switch (obj) { case null: case R(): // Multiple case labels System.out.println("Either null or an R"); break; default: }
14.11.1.1 Exhaustive Switch Blocks
The switch block of a switch
expression or
switch
statement is exhaustive for a selector
expression e if one of the following cases applies:
There is a
default
label associated with the switch block.There is a
case
null,
default
label associated with the switch block.The set containing all the
case
constants andcase
patterns appearing in an unguardedcase
label (collectively known ascase
elements) associated with the switch block is non-empty and covers the type of the selector expression e.
A set of case
elements,
PCE, covers a
type T if one of the following cases applies:
PCE covers a type U where T and U have the same erasure.PCE contains a pattern that is unconditional (14.30.3) for T.T is a type variable with upper bound B and
PCE covers B.T is an intersection type T1
&
...&
Tn andPCE covers Ti, for one of the types Ti (1≤ i ≤ n).
- The type T is
boolean
orBoolean
and CE contains both the constanttrue
and the constantfalse
.
The type T is an enum class type E and
PCE contains all of the names of the enum constants of E.A
default
label is permitted, but not required, in the case where the names of all the enum constants appear ascase
constants. For example:enum E { F, G, H } static int testEnumExhaustive(E e) { return switch(e) { case F -> 0; case G -> 1; case H -> 2; // No default required! }; }
CE contains a type pattern with a primitive type P, and T is the wrapper class for the primitive type W, and the conversion from type W to type P is unconditionally exact (5.7.2).
Integer
is exhaustive for a typedouble
.static int doubleExhaustive(Integer i) { return switch (i) { case double p -> 0; }; }
Note that
null
is not in the domain of a primitive type. The execution of an exhaustive switch can fail with an error (aMatchException
is thrown) if it encountersnull
.
The type T names an
abstract
sealed
class orsealed
interface C and for every permitted direct subclass or subinterface D of C, one of the following two conditions holds:There is no type that both names D and is a subtype of T, or
There is a type U that both names D and is a subtype of T, and
PCE covers U.
A
default
label is permitted, but not required, in the case where the switch block exhausts all the permitted direct subclasses and subinterfaces of anabstract
sealed
class orsealed
interface. For example:sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final static int testExhaustive1(I i) { return switch(i) { case A a -> 0; case B b -> 1; case C c -> 2; // No default required! }; }
As the switch block contains
case
patterns that match against all values of typesA
,B
andC
, and no other instances of typeI
are permitted, this switch block is exhaustive.The fact that a permitted direct subclass or subinterface may only extend a particular parameterization of a generic
sealed
superclass or superinterface means that it may not always need to be considered when determining whether a switch block is exhaustive. For example:sealed interface J<X> permits D, E {} final class D<Y> implements J<String> {} final class E<X> implements J<X> {} static int testExhaustive2(J<Integer> ji) { return switch(ji) { // Exhaustive! case E<Integer> e -> 42; }; }
As the selector expression has type
J
<Integer
> the permitted direct subclassD
need not be considered as there is no possibility that the value ofji
can be an instance ofD
.The type T names a record class R, and
PCE contains a record pattern p with a type that names R and for every record component of R of type U, if any, the singleton set containing the corresponding component pattern of p covers U.A record pattern whose component patterns all cover the type of the corresponding record component is considered to cover the record type. For example:
record Test<X>(Object o, X x){} static int testExhaustiveRecordPattern(Test<String> r) { return switch(r) { // Exhaustive! case Test<String>(Object o, String s) -> 0; }; }
PCE rewrites to a setQQE andQQE covers T.A set of case elements,
PCE, rewrites to the setQQE, if a subset ofPCE reduces to a pattern p, andQQE consists of the remaining elements ofPCE along with the pattern p.A non-empty set of patterns, RP, reduces to a single pattern rp if one of the following holds:
RP covers some type U, and rp is a type pattern of type U.
RP consists of record patterns whose types all erase to the same record class R with k (k≥1) components and there is a distinguished component cr (1≤r≤k) of R such that for every other component ci (1≤i≤k, i≠r) the set containing the component patterns from the record patterns corresponding to component ci is equivalent to a single pattern qi, the set containing the component patterns from the record patterns corresponding to the component cr reduces to a single pattern q, and rp is the record pattern of type R with a pattern list consisting of the patterns q1, ..., qr-1, q, qr+1, ..., qk.
A non-empty set of patterns EP is equivalent to a single pattern ep if one of the following holds:
EP consists of type patterns whose types all have the same erasure T, and ep is a type pattern of type T.
EP consists of record patterns whose types all erase to the same record class R with k (k≥1) components and for every record component the set containing the corresponding component patterns from the record patterns is equivalent to a single pattern qj (1≤j≤k), and ep is the record pattern of type R with a component pattern list consisting of the component patterns q1,...qk.
Ordinarily record patterns match only a subset of the values of the record type. However, a number of record patterns in a switch block can combine to actually match all of the values of the record type. For example:
sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final record Box(I i) {} int testExhaustiveRecordPatterns(Box b) { return switch (b) { // Exhaustive! case Box(A a) -> 0; case Box(B b) -> 1; case Box(C c) -> 2; }; }
Determining whether this switch block is exhaustive requires the analysis of the combination of the record patterns. The set containing the record pattern
Box(I i)
covers the typeBox
, and so the set containing the patternsBox(A a)
,Box(B b)
, andBox(C c)
can be rewritten to the set containing the patternBox(I i)
. This is because the set containing the patternsA a
,B b
,C c
reduces to the patternI i
(because the same set covers the typeI
), and thus the set containing the patternsBox(A a)
,Box(B b)
,Box(C c)
reduces to the patternBox(I i)
.
However, rewriting a set of record patterns is not always so simple. For example:
record IPair(I i, I j){} int testNonExhaustiveRecordPatterns(IPair p) { return switch (p) { // Not Exhaustive! case IPair(A a, A a) -> 0; case IPair(B b, B b) -> 1; case IPair(C c, C c) -> 2; }; }
It is tempting to apply the logic from the previous example to rewrite the set containing the patterns
IPair(A a, A a)
,IPair(B b, B b)
,IPair(C c, C c)
to the set containing the patternIPair(I i, I j)
, and hence conclude that the switch block exhausts the typeIPair
. But this is incorrect as, for example, the switch block does not actually have a label that matches anIPair
value whose first component is anA
value, and second component is aB
value. It is only valid to combine record patterns on one component if they match the same values in the other components. For example, the set containing the three record patternsIPair(A a, I i)
,IPair(B b, I i)
, andIPair(C c, I i)
can be reduced to the patternIPair(I j, I i)
.
A switch
statement or expression is exhaustive
if its switch block is exhaustive for the selector expression.
14.11.1.2 Determining which Switch Label Applies at Run Time
Both the execution of a switch
statement (14.11.3)
and the evaluation of a switch
expression (15.28.2)
need to determine if a switch label associated with the switch block
applies to the value of the selector expression. This proceeds
as follows:
If T is a reference type and if
Ifthe value is the null reference, then acase
label with anull
literal applies.If the value is not the null reference, thenThen we determine the first (if any)case
label in the switch block that applies to the value as follows:If T is a reference type and if the value is not the null reference, then, a
Acase
label with acase
constant c applies to a value of typeCharacter
,Byte
,Short
,orInteger
,Long
,Float
,Double
orBoolean
, if the value is first subjected to unboxing conversion (5.1.8) and the constant c is equal to the unboxed value.Any unboxing conversion will complete normally as the value being unboxed is guaranteed not to be the null reference.
Equality is defined in terms of the
==
operator (15.21).If T is a primitive type, then a
Acase
label with acase
constant c applies to a value that is of typechar
,byte
,short
,int
,orlong
,float
,double
,boolean
, orString
or an enum type if the constant c is equal to the value.Equality is defined in terms of the
==
operator (15.21) for the integral types and the boolean type, and in terms of representation equivalence (java.lang.Double) for the floating-point types.unlessIf the value is aString
,in which caseequality is defined in terms of theequals
method of classString
.Determining that a
case
label with acase
pattern p applies to a value proceeds first by checking if the value matches the pattern p (14.30.2).If pattern matching completes abruptly then the process of determining which switch label applies completes abruptly for the same reason.
If pattern matching succeeds and the
case
label is unguarded then thiscase
label applies.If pattern matching succeeds and the
case
label is guarded, then the guard is evaluated. If the result is of typeBoolean
, it is subjected to unboxing conversion (5.1.8).If evaluation of the guard or the subsequent unboxing conversion (if any) completes abruptly for some reason, the process of determining which switch label applies completes abruptly for the same reason.
Otherwise, if the resulting value is
true
then thecase
label applies.Determining that a
case
label withcase
patterns p1, ..., pn (n≥1) applies to a value proceeds by finding the first (if any)case
pattern pi (1≤i≤n) that applies to the value.Determining that a
case
pattern applies to a value proceeds first by checking the value matches the pattern (14.30.2). Then:If pattern matching completes abruptly then the whole process of determining which switch label applies completes abruptly for the same reason.
If pattern matching succeeds and the
case
label is unguarded then thiscase
pattern applies.If pattern matching succeeds and the
case
label is guarded, then the guard is evaluated. If the result is of typeBoolean
, it is subjected to unboxing conversion (5.1.8).If evaluation of the guard or the subsequent unboxing conversion (if any) completes abruptly for some reason, then the whole process of determining which switch label applies completes abruptly for the same reason.
Otherwise, if the resulting value is
true
then thecase
pattern applies.
A
case
null,
default
label applies to every value.
If the value is not the null reference, when T is a reference type, and no
case
label applies according to the rules of step 2, but there is adefault
label associated with the switch block, then thedefault
label applies.
A single
case
label can contain severalcase
constants. The label applies to the value of the selector expression if any one of its constants is equal to the value of the selector expression. For example, in the following code, thecase
label applies if the enum variableday
is either one of the enum constants shown:switch (day) { ... case SATURDAY, SUNDAY : System.out.println("It's the weekend!"); break; ... }
If a
case
label with acase
pattern applies, then this is because the process of pattern matching the value against the pattern has succeeded (14.30.2). If a value successfully matches a pattern then the process of pattern matching initializes any pattern variables declared by the pattern.
In C and C++ the body of a
switch
statement can be a statement and statements withcase
labels do not have to be immediately contained by that statement. Consider the simple loop:for (i = 0; i < n; ++i) foo();
where
n
is known to be positive. A trick known as Duff's device can be used in C or C++ to unroll the loop, but this is not valid code in the Java programming language:int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Great C hack, Tom, case 7: foo(); // but it's not valid here. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q > 0); }
Fortunately, this trick does not seem to be widely known or used. Moreover, it is less needed nowadays; this sort of code transformation is properly in the province of state-of-the-art optimizing compilers.
14.11.2 The Switch Block of a switch
Statement
In addition to the general rules for switch blocks (14.11.1), there are further rules for switch
blocks in switch
statements.
An enhanced switch
statement is one where
either (i) the type of the selector expression is not char
,
byte
, short
, int
,
Character
, Byte
, Short
,
Integer
, String
, or an enum type, or (ii)
there is a case
pattern or null
literal
associated with the switch block.
Prior to Java SE 23 only
char
,byte
,short
,int
and reference types where supported as the possible selector type of the switch. Since this restriction is lifted in Java SE 23, switch statements with the selector types offloat
,double
,long
,boolean
or one of their wrapper types are also considered as enhanced.
All of the following must be true for the switch block of a
switch
statement, or a compile-time error occurs:
No more than one
default
label is associated with theswitch
block.Every switch rule expression in the switch block is a statement expression (14.8).
switch
statements differ fromswitch
expressions in terms of which expressions may appear to the right of an arrow (->
) in the switch block, that is, which expressions may be used as switch rule expressions. In aswitch
statement, only a statement expression may be used as a switch rule expression, but in aswitch
expression, any expression may be used (15.28.1).If the
switch
statement is an enhancedswitch
statement, then it must be exhaustive (14.11.1.1).
Prior to Java SE 21,
switch
statements (andswitch
expressions) were limited in two ways: (i) the type of the selector expression was restricted to either an integral type (excludinglong
), an enum type, orString
and (ii) nocase
null
labels were supported. Moreover, unlikeswitch
expressions,switch
statements did not have to be exhaustive. This is often the cause of difficult-to-detect bugs, where no switch label applies and theswitch
statement will silently do nothing. For example:
enum E { A, B, C } E e = ...; switch (e) { case A -> System.out.println("A"); case B -> System.out.println("B"); // No case for C! }
In Java SE 21, in addition to supporting
case
patterns, the two limitations ofswitch
statements (andswitch
expressions) listed above were relaxed to (i) allow a selector expression of any reference type, and (ii) to allow acase
label with anull
literal. The designers of the Java programming language also decided that enhancedswitch
statements should align withswitch
expressions and be required to be exhaustive. This is often achieved with the addition of a trivialdefault
label. For example, the following enhancedswitch
statement is not exhaustive:
Object o = ...; switch (o) { // Error - non-exhaustive switch! case String s -> System.out.println("A string!"); }
but it can easily be made exhaustive:
Object o = ...; switch (o) { case String s -> System.out.println("A string!"); default -> {} }
For compatibility reasons,
switch
statements that are not enhancedswitch
statements are not required to be exhaustive.
14.30 Patterns
14.30.1 Kinds of Patterns
A type pattern is used to test whether a value is an instance of the type appearing in the pattern. A record pattern is used to test whether a value is an instance of a record class type and, if it is, to recursively perform pattern matching on the record component values.
- Pattern:
- TypePattern
- RecordPattern
- TypePattern:
- LocalVariableDeclaration
- RecordPattern:
-
ReferenceType
(
[ComponentPatternList])
- ComponentPatternList:
-
ComponentPattern {
,
ComponentPattern } - ComponentPattern:
- Pattern
- MatchAllPattern
- MatchAllPattern:
-
_
The following productions from 4.3, 8.3, 8.4.1, and 14.4 are shown here for convenience:
- LocalVariableDeclaration:
- {VariableModifier} LocalVariableType VariableDeclaratorList
- VariableModifier:
- Annotation
final
- LocalVariableType:
- UnannType
var
- VariableDeclaratorList:
- VariableDeclarator {
,
VariableDeclarator}- VariableDeclarator:
- VariableDeclaratorId [
=
VariableInitializer]- VariableDeclaratorId:
- Identifier [Dims]
_
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
See 8.3 for UnannType.
A pattern is nested in a record pattern if (1) it appears directly in the component pattern list of the record pattern, or (2) it is nested in a record pattern that appears directly in the component pattern list of the record pattern. A pattern is top level if it is not nested in a record pattern.
A type pattern declares one local variable, known as a pattern variable. If the declaration includes an identifier then this specifies the name of the pattern variable, otherwise the pattern variable is called an unnamed pattern variable.
The rules for a local variable declared in a type pattern are specified in 14.4. In addition, all of the following must be true, or a compile-time error occurs:
The LocalVariableType in a top level type pattern denotes
a referenceany type (and furthermore is notvar
).The VariableDeclaratorList consists of a single VariableDeclarator.
The VariableDeclarator has no initializer.
The VariableDeclaratorId has no bracket pairs.
The type of a pattern variable declared in a top level type pattern
is the reference type denoted by
LocalVariableType.
The type of a pattern variable declared in a nested type pattern is determined as follows:
If the LocalVariableType is UnannType then the type of the pattern variable is denoted by UnannType.
If the LocalVariableType is
var
then the type pattern must appear directly in the component pattern list of a record pattern, or a compile-time error occurs.Let R be the type of the record pattern, and let T be the type of the corresponding component field in R (8.10.3). The type of the pattern variable is the upward projection of T with respect to all synthetic type variables mentioned by T.
Consider the following declaration of a record class:
record R<T>(ArrayList<T> a){}
Given the record pattern
R
<String
>(var b)
, the type of the pattern variableb
isArrayList
<String
>.
A type pattern is said to be null matching if it is appears directly in the component pattern list of a record pattern with type R, where the corresponding record component of R has type U, U is a reference type and the type pattern is unconditional for the type U (14.30.3).
Note that this compile-time property of type patterns is used in the run-time process of pattern matching (14.30.2), so it is associated with the type pattern for use at run time.
A record pattern consists of a ReferenceType and a component pattern list containing component patterns, if any. If ReferenceType is not a record class type (8.10) then a compile-time error occurs.
If the ReferenceType is a raw type, then the type of the record pattern is inferred, as described in 18.5.5. It is a compile-time error if no type can be inferred for the record pattern.
If the ReferenceType (or any part of it) is annotated then a compile-time error occurs.
Future versions of the Java Programming Language may lift this restriction on annotations.
Otherwise, the type of the record pattern is ReferenceType.
The length of the record pattern's component pattern list must be the same as the length of the record component list in the declaration of the record class named by ReferenceType otherwise a compile-time error occurs.
A record pattern does not directly declare any pattern variables itself, but may contain declarations of pattern variables in the component pattern list.
It is a compile-time error if a record pattern contains more than one declaration of a pattern variable with the same name.
The match-all pattern is a special pattern that declares no pattern variables and can only appear directly in the component pattern list of a record pattern r.
Let R be the type of the record pattern r, and let T be the type of the corresponding component field in R (8.10.3). The type of the match-all pattern is the upward projection of T with respect to all synthetic type variables mentioned by T.
14.30.2 Pattern Matching
Pattern matching is the process of testing a value against a pattern at run time. Pattern matching is distinct from statement execution (14.1) and expression evaluation (15.1). If a value successfully matches a pattern, then the process of pattern matching will initialize all the pattern variables declared by the pattern, if any.
The process of pattern matching may involve expression evaluation or
statement execution. Accordingly, pattern matching is said to
complete abruptly if evaluation of an expression or execution
of a statement completes abruptly. An abrupt completion always has an
associated reason, which is always a throw
with a given
value. Pattern matching is said to complete normally if it does
not complete abruptly.
The rules for determining whether a value matches a pattern, and for initializing pattern variables, are as follows:
The null reference matches a type pattern if the type pattern is null-matching (14.30.1); and does not match otherwise.
If the null reference matches, then the pattern variable declared by the type pattern is initialized to the null reference.
If the null reference does not match, then the pattern variable declared by the type pattern is not initialized.
A value v that is not the null reference matches a type pattern of type T if v
cancould be converted by a testing conversion(5.7)that is exact (5.7.1) to the target type Twithout raising a; and does not match otherwise.ClassCastException
If v matches, then the pattern variable declared by the type pattern is initialized to v.
If v does not match, then the pattern variable declared by the type pattern is not initialized.
The null reference does not match a record pattern.
In this case, any pattern variables appearing in declarations contained in the record pattern are not initialized.
A value v that is not the null reference matches a record pattern with type R and component pattern list L if (i) v
cancould be converted by a testing conversion(5.7)that is exact (5.7.1) to the target type Rwithout raising a; and (ii) each record component of v matches the corresponding component pattern in L; and does not match otherwise.ClassCastException
Each record component of v is determined by invoking the accessor method of v corresponding to that component. If execution of the invocation of the accessor method completes abruptly for reason S, then pattern matching completes abruptly by throwing a
MatchException
with cause S.A pattern variable declared by a pattern appearing in the component pattern list of a record pattern is initialized only if all the patterns in the list match.
Every value v matches a match-all pattern.
14.30.3 Properties of Patterns
A pattern p is said to be applicable at a type T if one of the following rules apply:
A type pattern that declares a pattern variable of a
referencetype U is applicable at areferencetype T if there is a testing conversion (5.7) from type T to type U.A type pattern that declares a pattern variable of a primitive type P is applicable at the type P.A record pattern with type R and pattern list L is applicable at type T if (i) there is a testing conversion (5.7) from type T to type R, and (ii) for every component pattern p appearing in L, if any, p is applicable at the type of the corresponding component field in R.
A match-all pattern is applicable at every type T.
A pattern p is said to be unconditional for a type
T if every value of type T will
matchmatches p, and so the testing
aspect of pattern matching could be elidedso that pattern
matching requires no action at run time. It is defined as
follows:
- A type pattern that declares a pattern variable of a
referencetype S is unconditional for areferencetype T ifthe erasure of T is a subtype of the erasure of Sthere is a testing conversion that is unconditionally exact (5.7.2) from |T| to |S|.
- A type pattern that declares a pattern variable of a primitive type P is unconditional for the type P.
- A match-all pattern is unconditional for every type T.
Note that no record pattern is unconditional because the null reference does not match any record pattern.
A pattern p is said to dominate another pattern q if every value that matches q also matches p, and is defined as follows:
A pattern p dominates a type pattern that declares a pattern variable of type T if p is unconditional for T.
A pattern p dominates a record pattern with type R if p is unconditional for R.
A record pattern with type R and pattern list L dominates another record pattern with type S and pattern list M if (i) R and S name the same record class, and (ii) every component pattern, if any, in L dominates the corresponding component pattern in M.
A pattern p dominates a match-all pattern with type T if p is unconditional for T.
Chapter 15: Expressions
15.5 Expressions and Run-Time Checks
If the type of an expression is a primitive type, then the value of the expression is of that same primitive type.
If the type of an expression is a reference type, then the class of
the referenced object, or even whether the value is a reference to an
object rather than null
, is not necessarily known at
compile time.
There are a few places in the Java programming language where the actual class of a referenced object or value of a primitive type affects program execution in a manner that cannot be deduced from the type of the expression. They are as follows:
Method invocation (15.12). The particular method used for an invocation
o.m(...)
is chosen based on the methods that are part of the class or interface that is the type ofo
. For instance methods, the class of the object referenced by the run-time value ofo
participates because a subclass may override a specific method already declared in a parent class so that this overriding method is invoked. (The overriding method may or may not choose to further invoke the original overriddenm
method.)The
instanceof
operator (15.20.2). An expressionwhose type is a reference typemay be tested usinginstanceof
to find out whether a) the class of the object referenced by the run-time value of the expression or b) the primitive type of the value of the expression may be converted to some otherreferencetype.Casting (15.16). The class of the object referenced by the run-time value of the operand expression might not be compatible with the type specified by the cast operator. For reference types, this may require a run-time check that throws an exception if the class of the referenced object, as determined at run time, cannot be converted to the target type.
Assignment to an array component of reference type (10.5, 15.13, 15.26.1). The type-checking rules allow the array type S
[]
to be treated as a subtype of T[]
if S is a subtype of T, but this requires a run-time check for assignment to an array component, similar to the check performed for a cast.Exception handling (14.20). An exception is caught by a
catch
clause only if the class of the thrown exception object is aninstanceof
the type of the formal parameter of thecatch
clause.
Situations where the class of an object is not statically known may lead to run-time type errors.
In addition, there are situations where the statically known type may not be accurate at run time. Such situations can arise in a program that gives rise to compile-time unchecked warnings. Such warnings are given in response to operations that cannot be statically guaranteed to be safe, and cannot immediately be subjected to dynamic checking because they involve non-reifiable types (4.7). As a result, dynamic checks later in the course of program execution may detect inconsistencies and result in run-time type errors.
A run-time type error can occur only in these situations:
In a cast, when the actual class of the object referenced by the value of the operand expression is not compatible with the target type specified by the cast operator (5.5, 15.16); in this case a
ClassCastException
is thrown.In an automatically generated cast introduced to ensure the validity of an operation on a non-reifiable type (4.7).
In an assignment to an array component of reference type, when the actual class of the object referenced by the value to be assigned is not compatible with the actual run-time component type of the array (10.5, 15.13, 15.26.1); in this case an
ArrayStoreException
is thrown.When an exception is not caught by any
catch
clause of atry
statement (14.20); in this case the thread of control that encountered the exception first attempts to invoke an uncaught exception handler (11.3) and then terminates.
15.16 Cast Expressions
A cast expression converts, at run time, a value of one numeric type
to a similar value of another numeric type; or confirms, at compile
time, that the type of an expression is boolean
; or checks,
at run time, that a reference value refers to an object either whose
class is compatible with a specified reference type or list of reference
types, or which embodies a value of a primitive type.
- CastExpression:
-
(
PrimitiveType)
UnaryExpression -
(
ReferenceType {AdditionalBound})
UnaryExpressionNotPlusMinus -
(
ReferenceType {AdditionalBound})
LambdaExpression #
The following production from [4.4] is shown here for convenience:
- AdditionalBound:
&
InterfaceType
The parentheses and the type or list of types they contain are sometimes called the cast operator.
If the cast operator contains a list of types, that is, a ReferenceType followed by one or more AdditionalBound terms, then all of the following must be true, or a compile-time error occurs:
ReferenceType must denote a class or interface type.
The erasures (4.6) of all the listed types must be pairwise different.
No two listed types may be subtypes of different parameterizations of the same generic interface.
The target type for the casting context (5.5) introduced by the cast expression is either the PrimitiveType or the ReferenceType (if not followed by AdditionalBound terms) appearing in the cast operator, or the intersection type denoted by the ReferenceType and AdditionalBound terms appearing in the cast operator.
The type of a cast expression is the result of applying capture conversion (5.1.10) to this target type.
Casts can be used to explicitly "tag" a lambda expression or a method reference expression with a particular target type. To provide an appropriate degree of flexibility, the target type may be a list of types denoting an intersection type, provided the intersection induces a functional interface (9.8).
The result of a cast expression is not a variable, but a value, even if the result of evaluating the operand expression is a variable.
If the compile-time type of the operand cannot be converted by casting conversion (5.5) to the target type specified by the cast operator, then a compile-time error occurs.
Otherwise, at run time, the operand value is converted (if necessary) by casting conversion to the target type specified by the cast operator.
A ClassCastException
is thrown if a cast is found at run
time to be impermissible.
Some casts result in an error at compile time. Some casts can be proven, at compile time, always to be correct at run time. For example, it is always correct to convert a value of a class type to the type of its superclass; such a cast should require no special action at run time. Finally, some casts cannot be proven to be either always correct or always incorrect at compile time. Such casts require a test at run time. See 5.5 for details.
When the result of a cast expression is the same numerical value
exactly or no ClassCastException
was throwed, the
conversion is characterized as exact (5.7.1).
15.20 Relational Operators
15.20.2 The instanceof
Operator
An instanceof
expression may perform either type
comparison or pattern matching.
- InstanceofExpression:
-
RelationalExpression
instanceof
ReferenceTypeType -
RelationalExpression
instanceof
Pattern
If the operand to the right of the instanceof
keyword is
a ReferenceTypeType, then
the instanceof
keyword is the type comparison
operator.
If the operand to the right of the instanceof
keyword is
a Pattern, then the instanceof
keyword is the
pattern match operator.
The type of the expression RelationalExpression can be a reference type, a primitive type or the null type.
The following rules apply when instanceof
is the type
comparison operator:
The type of the expression RelationalExpression must be a reference typeor the null type, or a compile-time error occurs.The type of the RelationalExpression
must be checked cast compatible with the ReferenceType (5.5)must be convertible by testing conversion to the Type (5.7), or a compile-time error occurs.At run time, the result of the type comparison operator is determined as follows:
If the value of the RelationalExpression is the null reference (4.1), then the result is
false
.If the value of the RelationalExpression is not the null reference, then the result is
true
if the value could becast to the ReferenceType without raising aconverted to the Type by a testing conversion that is exact (5.7.1), andClassCastException
false
otherwise.
The following rules apply when instanceof
is the pattern
match operator:
The type of the expression RelationalExpression must be a reference typeor the null type, or a compile-time error occurs.The Pattern must be applicable at the type of the expression RelationalExpression (14.30.3), or a compile-time error occurs.
At run time, the result of the pattern match operator is determined as follows:
If the value of the RelationalExpression is the null reference, then the result is
false
.If the value of the RelationalExpression is not the null reference, then the result is
true
if the value matches the Pattern (14.30.2), andfalse
otherwise.A side effect of a
true
result is that all the pattern variables declared in Pattern, if any, will be initialized.
Example 15.20.2-1. The Type Comparison Operator
class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
public static void main(String[] args) {
Point p = new Point();
Element e = new Element();
if (e instanceof Point) { // compile-time error
System.out.println("I get your point!");
p = (Point)e; // compile-time error
}
}
}
This program results in two compile-time errors. The cast
(Point)e
is incorrect because no instance of
Element
or any of its possible subclasses (none are shown
here) could possibly be an instance of any subclass of
Point
. The instanceof
expression is incorrect
for exactly the same reason. If, on the other hand, the class
Point
were a subclass of Element
(an
admittedly strange notion in this example):
class Point extends Element { int x, y; }
then the cast would be possible, though it would require a run-time
check, and the instanceof
expression would then be sensible
and valid. The cast (Point)e
would never raise an exception
because it would not be executed if the value of e
could
not correctly be cast to type Point
.
Prior to Java SE 16, the ReferenceType operand of a type
comparison operator was required to be reifiable (4.7).
This prevented the use of a parameterized type unless all its type
arguments were wildcards. The requirement was lifted in Java SE 16 to
allow more parameterized types to be used. For example, in the following
program, it is legal to test whether the method parameter
x
, with static type List<Integer>
, has a
more "refined" parameterized type ArrayList<Integer>
at run time:
import java.util.ArrayList;
import java.util.List;
class Test2 {
public static void main(String[] args) {
List<Integer> x = new ArrayList<Integer>();
if (x instanceof ArrayList<Integer>) { // OK
System.out.println("ArrayList of Integers");
}
if (x instanceof ArrayList<String>) { // error
System.out.println("ArrayList of Strings");
}
if (x instanceof ArrayList<Object>) { // error
System.out.println("ArrayList of Objects");
}
}
}
The first instanceof
expression is legal because there
is a casting conversion from List<Integer>
to
ArrayList<Integer>
. However, the second and third
instanceof
expressions both cause a compile-time error
because there is no casting conversion from
List<Integer>
to ArrayList<String>
or ArrayList<Object>
.
15.28 switch
Expressions
A switch
expression transfers control to one of several
statements or expressions, depending on the value of an expression; all
possible values of that expression must be handled, and all of the
several statements and expressions must produce a value for the result
of the switch
expression.
- SwitchExpression:
-
switch
(
Expression)
SwitchBlock
The Expression is called the selector expression.
The type of the selector expression must be may be any
type.char
,
byte
, short
, int
, or a reference
type, or a compile-time error occurs
The body of both a
switch
expression and aswitch
statement (14.11) is called a switch block. General rules which apply to all switch blocks, whether they appear inswitch
expressions orswitch
statements, are given in 14.11.1. The following productions from 14.11.1 are shown here for convenience:
- SwitchBlock:
{
SwitchRule {SwitchRule}}
{
{SwitchBlockStatementGroup} {SwitchLabel:
}}
- SwitchRule:
- SwitchLabel
->
Expression;
- SwitchLabel
->
Block- SwitchLabel
->
ThrowStatement- SwitchBlockStatementGroup:
- SwitchLabel
:
{SwitchLabel:
} BlockStatements- SwitchLabel:
case
CaseConstant {,
CaseConstant}case
null
[,
default
]case
CasePattern {,
CasePattern} [Guard]default
- CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern
- Guard:
when
Expression
Chapter 19: Syntax
This chapter repeats the syntactic grammar given in Chapters 4, 6-10, 14, and 15, as well as key parts of the lexical grammar from Chapter 3, using the notation from 2.4.
The production for InstanceofExpression is the only one that changes. The rest of this section is unchanged.
Productions from 15
- InstanceofExpression:
-
RelationalExpression
instanceof
ReferenceTypeType -
RelationalExpression
instanceof
Pattern