Inselupdate: Record-Patterns in Java 21

Führen wir für die folgenden Beispiele ein Record für Punkte ein:

record Point( int x, int y ) {}

Nun möchten wir überprüfen, ob die X-Y-Koordinaten eines Punktes auf null stehen. Dafür erstellen wir eine Methode namens isZeroPoint(…), die alle Objekttypen akzeptiert und false zurückgibt, wenn es sich nicht um einen Punkt handelt:

static boolean isZeroPoint( Point object ) {

  if ( object instanceof Point ) {

    Point point = (Point) object;

    return point.x() == 0 && point.y() == 0;

  }

  return false;

}

Eine kompaktere Version des Tests, die auf eine Zwischenvariable verzichtet, kann so aussehen:

return object instanceof Point && ((Point) object).x() == 0 && ((Point) object).y() == 0;

Lesbarerer ist es nicht.

Der Einsatz einer Pattern-Variable verbessert die Lesbarkeit und Klarheit des Codes:

static boolean isZeroPoint( Object object ) {

  return object instanceof Point p && p.x() == 0 && p.y() == 0;

}

Der Test p.x() == 0 && p.y() == 0 lässt sich mit einem Trick noch weiter verkürzen:
static boolean isZeroPoint( Object object ) {

  return object instanceof Point p && (p.x() | p.y()) == 0;

}

Wenn zwei Zahlen mittels der bitweisen ODER-Operation verknüpft werden und eine davon nicht null ist, wird auch das Ergebnis nicht null sein. Dies ermöglicht eine elegante und kompakte Überprüfung der Nullbedingung für beide Koordinaten gleichzeitig.

Record-Pattern einsetzen

Auffällig an der bisherigen Lösung ist die Notwendigkeit einer Point-Variable p, um auf p.x() und p.y() zuzugreifen. Java 21 führt Record-Pattern[1] ein, die in anderen Programmiersprachen als Destrukturierung bezeichnet werden. Record-Pattern können sowohl bei instanceof als auch bei switch verwendet werden. Hier ist ein Beispiel für instanceof, wodurch isZeroPoint(…) etwas kürzer wird:

static boolean isZeroPoint( Point point ) {

  return point instanceof Point( int a, int b ) && (a | b) == 0;

}

Der Teil Point(int a, int b) nennt sich Record-Pattern. Nach einer Übereinstimmung werden neue lokale Variablen a und b eingeführt, die vom Punkt die Koordinaten enthalten. Das heißt, a wird mit point.x() und b mit point.y() belegt. Die Variablennamen müssen nicht mit den Record-Komponentennamen übereinstimmen; in diesem Fall heißen sie a und b, x und y wären aber möglich. Wichtig ist, alle Record-Komponenten aufzulisten; keine Record-Komponente darf ausgelassen werden.

Kommen wir von Punkten zu Linien. Betrachten wir ein neues Record:

record Line( Point start, Point end ) {}

Schreiben wir eine zweite Methode isZeroLine(…), die überprüft, ob die beiden Punkte der Linie Null sind oder nicht. Beginnen wir mit einer Pattern-Variablen:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line line

         && (  line.start().x() | line.start().y()

             | line.end().x()   | line.end().y() ) == 0;

}

Da es sich bei Line um einen Record handelt, kann das Record-Pattern angewendet werden:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line( Point start, Point end )

         && (start.x() | start.y() | end.x() | end.y()) == 0;

}

Die Datentypen können mit var abgekürzt werden:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line( var start, var end )

         && (start.x() | start.y() | end.x() | end.y()) == 0;

}

Geschachtelte Record-Pattern

Record-Pattern können auch verschachtelt werden, um komplexere Strukturen abzubilden:

static boolean isZeroLine( Object object ) {

  return object instanceof Line(

    Point( int x1, int y1 ), Point( int x2, int y2 )

   ) && (x1 | y1 | x2 | y2) == 0;

}

Das Code-Volumen schrumpft nicht, daher ist es Geschmacksache, ob die Variante besser ist.

Record-Pattern bei switch

Das Pattern-Matching bei switch ist in Java 21 noch leistungsfähiger geworden. Erstellen wir eine zweite Methode isZero(Object), die sowohl Punkte als auch Linien überprüfen kann. Lösen wir die Aufgabe zuerst wie bekannt mit Pattern-Variablen:

static boolean isZero( Object o ) {

  return switch ( o ) {

    case Point p -> (p.x() | p.y()) == 0;

    case Line l -> (l.start().x() | l.start().y() | l.end().x() | l.end().y()) == 0;

    default -> false;

  };

}

Neben instanceof sind Record-Pattern auch bei switch-case möglich. Die Methode kann wie folgt umgeschrieben werden:

static boolean isZero( Object o ) {

  return switch ( o ) {

    case Point( int x, int y ) -> (x | y) == 0;

    case Line( Point s, Point e ) -> isZero( s ) && isZero( e );

    default -> false;

  };

}

Bei der Linie lässt sich das Record-Pattern wieder schachteln:

static boolean isZero( Object o ) {

  return switch ( o ) {

    …

    case Line( Point( int x1, int y1 ), Point( int x2, int y2 ) )

          -> (x1 | y1 | x2 | y2) == 0;

    …

  };

}

Allerdings ist auch hier der Code wieder länger.

Pattern-Matching mit Record-Pattern und when

Wir haben gesehen, dass Pattern-Matching mit Record-Pattern zur Destrukturierung möglich ist. Auch lässt sich ein when für eine weitere Abfrage einsetzen. Die Bedingung hinter when kann auf die Pattern-Variable oder Variable aus dem Record-Pattern zugreifen.

Ein Beispiel: Wir möchten Candy und Book als Records implementieren:

record Candy( int calories ) {}

record Book( String title, int numberOfPages ) {}

Ein Block kann wie folgt Ausgaben zu unterschiedlichen Objekttypen formulieren:

Object object = new Candy( 120 );




switch ( object ) {

  case Candy candy

  when candy.calories > 10_000 ->

      System.out.println( "Are you trying to sweeten the whole world?" );




  case Candy candy ->

      System.out.println("Is this candy trying to start a dance party in my mouth?");




  case Book( var title, var pages )

  when pages > 100 ->

      System.out.println(

          "Looks like someone was on a mission to make the dictionary jealous." );




  case Book( var title, var pages )

  when title.isEmpty() ->

      System.out.println("Diving into books that forgot to introduce themselves.");




  case Book -> System.out.println( "Opening minds, one page at a time" );




  default -> System.out.println("Who knew boredom could be so three-dimensional?");

};

Das Beispiel zeigt Pattern-Matching sowohl ohne als auch mit Record-Pattern. Auch hier müssen wir erneut die Dominanz beachten. Es wäre inkorrekt, case Book -> über die anderen Fallblöcke case Book( var title, var pages ) when … zu setzen.

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

Ähnliche Beiträge

Schreibe einen Kommentar

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