Inselupdate: Pattern-Matching bei switch in Java 21

Wir haben bereits gesehen, dass der instanceof-Operator genutzt werden kann, um einen einzelnen Typ zu prüfen. Nun können wir diese Fallunterscheidung erweitern, um mehrere Typen zu testen. Nehmen wir an, dass die Zustände von Nap, Workout und Event im XML-Format gespeichert werden sollen. In diesem Kontext kann eine neue Methode die Abbildung auf XML übernehmen:

static String toXml( Object o ) {

  if ( o == null )

    return "<null />";

  if ( o instanceof Nap nap )

    return "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

  else if ( o instanceof Workout workout )

    return "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

        .formatted( workout.about, workout.duration,

                    workout.caloriesBurned );

  else if ( o instanceof Event event )

    return "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

  else

    return "<object />";

}

Gerade für solche Abfragen wurde in Java 21 die switch-Anweisung bzw. der switch-Ausdruck erweitert, um die Möglichkeit zu bieten, Typen zu testen und somit eine kaskadierte Typprüfung durchzuführen. Diese Erweiterung wird als Pattern-Matching bei switch (engl. Pattern Matching for switch)[1] bezeichnet, und es handelt sich um das Pendant zum Pattern-Matching bei instanceof.

Die Methode toXml(…) kann folgendermaßen umgeschrieben werden:

static String toXml( Object o ) {

  switch ( o ) {

    case null -> {

      return "<null />";

    }

    case Nap nap -> {

      return "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

    }

    case Workout workout -> {

      return "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

          .formatted( workout.about, workout.duration,

                      workout.caloriesBurned );

    }

    case Event event -> {

      return "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

    }

    default -> {

      return "<object />";

    }

  }

}

Die Überprüfung auf null ist mithilfe von case null möglich – eine weitere Erweiterung von Java 21.

Hinweis: Auch in Fällen, in denen eine switch-Anweisung verwendet wird, muss beim Pattern-Matching die Abdeckung vollständig sein. Daher ist in unserem Fall der default-Zweig erforderlich.

Die gewählte Lösung mit der switch-Anweisung ist zwar umsetzbar, doch da jeder switch-Block mit einem return endet, ist auch ein switch-Ausdruck möglich. Eine alternative Schreibeweise für toXml(…) lautet:

static String toXml( Object o ) {

  return switch ( o ) {

    case null -> "<null />";

    case Nap nap ->

        "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

    case Workout workout ->

        "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

            .formatted( workout.about, workout.duration,

                        workout.caloriesBurned );

    case Event event ->

        "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

    default -> "<object />";

  };

}

Dominanz

Normalerweise spielt bei einem switch-case die Reihenfolge der case-Blöcke keine Rolle –abgesehen vom Durchfallen, was jedoch bei -> nicht mehr existiert. Beim Pattern-Matching spielt die Reihenfolge sehr wohl eine Rolle und folgendes wäre nicht korrekt:

return switch ( o ) {      

  case null -> "<null />";

  case Event event -> "…";       // ☠ case-Label dominiert

  case Nap nap -> "…";

  …

}

Das case-Label case Event event dominiert das case-Label case Nap nap, daher müssen wir die Reihenfolge berücksichtigen. Im Übrigen gibt es bei der Ausnahmebehandlung einen ähnlichen Fall, Details finden meine Leser im Abschnitt 9.3.5, „Schon gefangen?“

Pattern-Matching mit when Wächter

Bisher haben wir in den case-Blocken nur den Typ überprüft. Es ist jedoch möglich, zusätzliche Bedingungen anzufügen. Hierfür wird nach der Pattern-Variable das Schlüsselwort when verwendet, gefolgt von einer Bedingung, die auf die Pattern-Variable:

Event event = new Nap();

switch ( event ) {

  case Nap nap

  when nap.duration < 10 ->

      System.out.println( "Too brief a sleep, not worth it." );




  case Nap nap

  when nap.duration > 100 -> System.out.println( "That's too long, wake up." );

 

  case Nap nap -> System.out.println( "Recharge and renew with every sleep" );




  case Workout workout -> System.out.println( "Elevate your fitness game");




  default -> {}

}

Hinter dem Schlüsselwort when kann eine Bedingung angegeben werden, die als Wächter (engl. guard) bezeichnet wird. Die Auswertung des case-Blocks erfolgt erst, wenn der Typ übereinstimmt und die Bedingung erfüllt ist. Die Prüfung auf den gleichen Typ kann mehrfach in verschiedenen Blöcken erfolgen. Wir müssen auch hier wieder die Dominanz berücksichtigen. So wäre es falsch, mit case Nap nap -> zu beginnen und erst dahinter ein case Nap nap when … -> zu setzen.

[1] https://openjdk.org/jeps/433

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert