61. Dealing with completeness (type coverage) in pattern labels for switch
In a nutshell, switch expressions and switch statements that use null and/or pattern labels should be exhaustive. In other words, we must cover with explicit switch case labels all the possible values. Let’s consider the following example:
class Vehicle {}
class Car extends Vehicle {}
class Van extends Vehicle {}
private static String whatAmI(Vehicle vehicle) {
return switch(vehicle) {
case Car car -> “You’re a car”;
case Van van -> “You’re a van”;
};
}
This snippet of code doesn’t compile. The error is clear: The switch expression does not cover all possible input values. The compiler complains because we don’t have a case pattern label for Vehicle. This base class can be legitimately used without being a Car or a Van, so it is a valid candidate for our switch. We can add a case Vehicle or a default label. If you know that Vehicle will remain an empty base class then probably you’ll go for a default label:
return switch(vehicle) {
case Car car -> “You’re a car”;
case Van van -> “You’re a van”;
default -> “I have no idea … what are you?”;
};
If we continue by adding another vehicle such as class Truck extends Vehicle {} then this will be handled by the default branch. If we plan to use Vehicle as an independent class (for instance, to enrich it with methods and functionalities) then we will prefer to add a case Vehicle as follows:
return switch(vehicle) {
case Car car -> “You’re a car”;
case Van van -> “You’re a van”;
case Vehicle v -> “You’re a vehicle”; // total pattern
};
This time, the Truck class will match the case Vehicle branch. Of course, we can add a case Truck as well.
The Vehicle v pattern is named a total type pattern. There are two labels that we can use to match all possible values: the total type pattern (for instance, a base class or an interface) and the default label. Generally speaking, a total pattern is a pattern that can be used instead of the default label.
In the previous example, we can accommodate all possible values via the total pattern or the default label but not both. This makes sense since the whatAmI(Vehicle vehicle) method gets a Vehicle as an argument. So, in this example, the selector expression can be only Vehicle or a subclass of Vehicle. How about modifying this method as whatAmI(Object o)?
private static String whatAmI(Object o) {
return switch(o) {
case Car car -> “You’re a car”;
case Van van -> “You’re a van”;
case Vehicle v -> “You’re a vehicle”; // optional
default -> “I have no idea … what are you?”;
};
}