67. Understanding the unconditional patterns and nulls in switch expressions
Let’s imagine that we use JDK 17 and we have the following code:
private static String drive(Vehicle v) {
return switch (v) {
case Truck truck -> "truck: " + truck;
case Van van -> "van: " + van;
case Vehicle vehicle -> "vehicle: " + vehicle.start();
};
}
drive(null);
Notice the call, drive(null)
. This call will hit the Vehicle vehicle
total pattern, so even null
values match total patterns. But, this means that the binding variable vehicle
will also be null
, which means that this branch is prone to NullPointerException
(for instance, if we call a hypothetical method, vehicle.start()
):
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "modern.challenge.Vehicle.start()" because "vehicle" is null
Because Vehicle vehicle
matches all possible values, it is known as a total pattern but also as an unconditional pattern since it matches everything unconditionally.
But, as we know from Problem 54, starting with JDK 17+ (JEP 427), we can have a pattern label for null
itself, so we can handle the previous shortcoming as follows:
return switch (v) {
case Truck truck -> "truck: " + truck;
case Van van -> "van: " + van;
case null -> "so, you don't have a vehicle?";
case Vehicle vehicle -> "vehicle: " + vehicle.start();
};
Yes, everybody agrees that adding a case null
between vehicles looks awkward. Adding it at the end is not an option since will raise a dominance issue. So, starting with JDK 19+, adding this case null
is no longer needed in this kind of scenario. Basically, the idea remains the same meaning that the unconditional pattern still only matches null
values so it will not allow the execution of that branch. Actually, when a null
value occurs, the switch
expressions will throw a NullPointerException
without even looking at the patterns. So, in JDK 19+, this code will throw an NPE right away:
return switch (v) {
case Truck truck -> "truck: " + truck;
case Van van -> "van: " + van;
// we can still use a null check
// case null -> "so, you don't have a vehicle?";
// total/unconditional pattern throw NPE immediately
case Vehicle vehicle -> "vehicle: " + vehicle.start();
};
The NPE message reveals that vehicle.start()
was never called. The NPE occurred much earlier:
Exception in thread "main" java.lang.NullPointerExceptionatjava.base/java.util.Objects.requireNonNull(Objects.java:233)
We will expand on this topic later when we will talk about Java records.