Dealing with completeness (type coverage) in pattern labels for switch 3 – Objects, Immutability, Switch Expressions, and Pattern Matching

This code compiles! There is no default label and no total pattern but the switch expression covers all possible values. How so?! This interface is declared as sealed via the sealed modifier:

public sealed interface ClassDesc
extends ConstantDesc, TypeDescriptor.OfField<ClassDesc>

Sealed interfaces/classes were introduced in JDK 17 (JEP 409) and we will cover this topic in chapter x. However, for now, is enough to know that sealing allows us to have fine-grained control of inheritance so classes and interfaces define their permitted subtypes. This means that the compiler can determine all possible values in a switch expression. Let’s consider a simpler example that starts as follows:

sealed interface Player {}
final class Tennis implements Player {}
final class Football implements Player {}
final class Snooker implements Player {} 

And, let’s have a switch expression covering all possible values for Player:

private static String trainPlayer(Player p) {     
  return switch (p) {
    case Tennis t -> “Training the tennis player …” + t;
    case Football f -> “Training the football player …” + f;
    case Snooker s -> “Training the snooker player …” + s;          
  };
}

The compiler is aware that the Player interface has only three implementations and all of them are covered via pattern labels. We can add a default label or the total pattern case Player player, but most probably you don’t want to do that. Imagine that we add a new implementation of the sealed Player interface named Golf:

final class Golf implements Player {}

If the switch expression has a default label then Golf values will be handed by this default branch. If we have the total pattern Player player then this pattern will handle the Golf values. On the other hand, if none of the default labels or total patterns is present, the compiler will immediately complain that the switch expression doesn’t cover all possible values. So, we are immediately informed, and once we add a case Golf g the error disappears. This way, we can easily maintain our code and have a guarantee that our switch expressions are always up to date and cover all possible values. The compiler will never miss the chance to inform us when a new implementation of Player is available.A similar logic applies to Java enums. Consider the following enum:

private enum PlayerTypes { TENNIS, FOOTBALL, SNOOKER }

The compiler is aware of all the possible values for PlayerTypes, so the following switch expression compiles successfully:

private static String createPlayer(PlayerTypes p) {     
  return switch (p) {
    case TENNIS -> “Creating a tennis player …”;
    case FOOTBALL -> “Creating a football player …”;
    case SNOOKER -> “Creating a snooker player …”;             
  };
}     

Again, we can add a default label or the total pattern, case PlayerTypes pt. But, if we add a new value in the enum (for instance, GOLF) the compiler will delegate the default label or the total pattern to handle it. On the other hand, if none of these are available, the compiler will immediately complain that the GOLF value is not covered, so we can add it (case GOLF g) and create a golf player whenever required.Done! Now you know how to deal with type coverage in switch expressions.

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2024 nickshade Please read our Terms and Privacy Policy. We also use Cookies.