13 Safe Casting with instanceof and switch
You can use the instanceof
operator to check at run time
whether a cast from one type to another is safe. A cast or conversion is safe if
there's no loss of information or exception thrown during the cast or conversion. You can
use the instanceof
operator to test whether a cast is safe between two
reference types or two primitive types. In addition, conversion safety affects how
switch
statements and expressions behave.
Note:
The ability to use theinstanceof
operator to test whether a cast is safe between two
primitive types is a preview feature. A preview feature is a feature whose design,
specification, and implementation are complete, but is not permanent. A preview feature
may exist in a different form or not at all in future Java SE releases. To compile and
run code that contains preview features, you must specify additional command-line
options. See Preview Language and VM
Features.
See JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) for additional information.
The following example uses instanceof
as the type
comparison operator to test whether the Shape s
is a
Circle
. If it is, then it's safe to cast Shape s
to a Circle
, which means that there's definitely a radius that you can
print:
public interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
//...
Shape s = new Rectangle(4,3);
if (s instanceof Circle) {
Circle c = (Circle)s;
System.out.println("Radius: " + c.radius());
}
The Java runtime throws a ClassCastException
if it can't
cast a reference type to another reference type. If you remove the s instanceof
Circle
expression from the example, the Java runtime throws an exception
when it tries to cast the Shape
, which is a Rectangle
,
to a Circle
:
Shape s = new Rectangle(4,3);
// ClassCastException: class Rectangle cannot be cast
// to class Circle
Circle c = (Circle)s;
System.out.println("Radius: " + c.radius());
When it comes to casting between primitive types, the Java runtime won't
ever throw a ClassCastException
. It will convert the value (if the
types are compatible), which might result in the loss of information about the value's
overall magnitude as well as its precision and range. The following example casts an
int
to a byte
:
int i = 2345323;
byte b = (byte)i;
System.out.println(b);
It prints the following output:
107
An improperly or unintendedly converted value, such as from 2345323
to
107
, or more subtly, from 2.05f
to
2
, can silently propagate through a program, causing potentially
elusive bugs.
As the primitive types int
and byte
differ
by their ranges, you could test that before casting the value:
if (i >= -128 && i <= 127) {
System.out.println((byte)i);
}
However, a clearer and more concise way to test whether a cast between two
primitive type is safe is to use instanceof
as the type comparison
operator:
if (i instanceof byte) {
System.out.println((byte)i);
}
Note that this example still has to cast the value i
to a
byte
. The instanceof
type comparison operator only
checks if i
is a byte
. It doesn't perform the actual
cast.
This also works with instanceof
as the pattern match
operator, which has the added benefit of performing the cast for you if the test is
successful:
if (i instanceof byte b) {
System.out.println(b);
}
Conversion Safety and switch
Consider the following example, which contains two switch
statements. The compiler generates an error for the first switch
statement:
float f = 100.01f;
switch (f) {
// error: the switch statement does not cover
// all possible input values
case int i ->
System.out.println(i + " as an int ");
}
switch (f) {
case double d ->
System.out.println(d + " as a double");
}
The first switch
statement isn't exhaustive because it only
processes int
values. The case label case int i
converts the float
value of the switch
statement's
selector expression to an int
value. This conversion is not safe, which
means that there's a possibility that information may be lost about the value's overall
magnitude as well as its precision and range. If this switch
statement
were permitted, then an improperly or unintendedly converted value, such as from
100.01f
to 100
, can propagate through a program,
causing subtle bugs. To help prevent this from happening, the compiler generates an
error.
The second switch
statement is exhaustive. The label
double d
converts the selector expression's float
to a double
. This is an unconditionally exact conversion, which
means that, at compile time, it's known that the conversion from the first type to the
second type is guaranteed not to lose information or throw an exception for any value.
Examples of unconditionally exact conversions include converting from
byte
to int
, from byte
to
Byte
, from int
to long
, and from
String to Object.
Whether a conversion between primitive types is unconditionally exact also
affects how patterns can dominate other patterns. (See Pattern Label Dominance.) In the following example, the compiler generates an error for the
type pattern byte b
:
switch (f) {
case int i ->
System.out.println(i + " as an int");
// error: this case label is dominated by a preceding case label
case byte b ->
System.out.println(b + " as a byte");
default ->
System.out.println(f + " as a float");
}
The type pattern int i
dominates the type pattern
byte b
because converting from byte
to
int
is unconditionally exact.