{"id":4683,"date":"2023-08-26T11:39:28","date_gmt":"2023-08-26T09:39:28","guid":{"rendered":"https:\/\/www.tutego.de\/blog\/javainsel\/?p=4683"},"modified":"2023-08-26T11:49:57","modified_gmt":"2023-08-26T09:49:57","slug":"record-patterns-in-java-21","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2023\/08\/record-patterns-in-java-21\/","title":{"rendered":"Inselupdate: Record-Patterns in Java 21"},"content":{"rendered":"<p>F\u00fchren wir f\u00fcr die folgenden Beispiele ein Record f\u00fcr Punkte ein:<\/p>\n<pre>record Point( int x, int y ) {}<\/pre>\n<p>Nun m\u00f6chten wir \u00fcberpr\u00fcfen, ob die X-Y-Koordinaten eines Punktes auf null stehen. Daf\u00fcr erstellen wir eine Methode namens isZeroPoint(\u2026), die alle Objekttypen akzeptiert und false zur\u00fcckgibt, wenn es sich nicht um einen Punkt handelt:<\/p>\n<pre>static boolean isZeroPoint( Point object ) {\r\n\r\n\u00a0 if ( object instanceof Point ) {\r\n\r\n\u00a0\u00a0\u00a0 Point point = (Point) object;\r\n\r\n\u00a0\u00a0\u00a0 return point.x() == 0 &amp;&amp; point.y() == 0;\r\n\r\n\u00a0 }\r\n\r\n\u00a0 return false;\r\n\r\n}<\/pre>\n<p>Eine kompaktere Version des Tests, die auf eine Zwischenvariable verzichtet, kann so aussehen:<\/p>\n<pre>return object instanceof Point &amp;&amp; ((Point) object).x() == 0 &amp;&amp; ((Point) object).y() == 0;<\/pre>\n<p>Lesbarerer ist es nicht.<\/p>\n<p>Der Einsatz einer Pattern-Variable verbessert die Lesbarkeit und Klarheit des Codes:<\/p>\n<pre>static boolean isZeroPoint( Object object ) {\r\n\r\n\u00a0 return object instanceof Point <strong>p<\/strong> &amp;&amp; p.x() == 0 &amp;&amp; p.y() == 0;\r\n\r\n}\r\n\r\nDer Test p.x() == 0 &amp;&amp; p.y() == 0 l\u00e4sst sich mit einem Trick noch weiter verk\u00fcrzen:<\/pre>\n<pre>static boolean isZeroPoint( Object object ) {\r\n\r\n\u00a0 return object instanceof Point p &amp;&amp; <strong>(p.x() | p.y()) == 0<\/strong>;\r\n\r\n}<\/pre>\n<p>Wenn zwei Zahlen mittels der bitweisen ODER-Operation verkn\u00fcpft werden und eine davon nicht null ist, wird auch das Ergebnis nicht null sein. Dies erm\u00f6glicht eine elegante und kompakte \u00dcberpr\u00fcfung der Nullbedingung f\u00fcr beide Koordinaten gleichzeitig.<\/p>\n<h4>Record-Pattern einsetzen<\/h4>\n<p>Auff\u00e4llig an der bisherigen L\u00f6sung ist die Notwendigkeit einer Point-Variable p, um auf p.x() und p.y() zuzugreifen. Java 21 f\u00fchrt Record-Pattern<a href=\"#_ftn1\" name=\"_ftnref1\">[1]<\/a> ein, die in anderen Programmiersprachen als Destrukturierung bezeichnet werden. Record-Pattern k\u00f6nnen sowohl bei instanceof als auch bei switch verwendet werden. Hier ist ein Beispiel f\u00fcr instanceof, wodurch isZeroPoint(\u2026) etwas k\u00fcrzer wird:<\/p>\n<pre>static boolean isZeroPoint( Point point ) {\r\n\r\n\u00a0 return point instanceof <strong>Point( int a, int b )<\/strong> &amp;&amp; (a | b) == 0;\r\n\r\n}<\/pre>\n<p>Der Teil Point(int a, int b) nennt sich Record-Pattern. Nach einer \u00dcbereinstimmung werden neue lokale Variablen a und b eingef\u00fchrt, die vom Punkt die Koordinaten enthalten. Das hei\u00dft, a wird mit point.x() und b mit point.y() belegt. Die Variablennamen m\u00fcssen nicht mit den Record-Komponentennamen \u00fcbereinstimmen; in diesem Fall hei\u00dfen sie a und b, x und y w\u00e4ren aber m\u00f6glich. Wichtig ist, alle Record-Komponenten aufzulisten; keine Record-Komponente darf ausgelassen werden.<\/p>\n<p>Kommen wir von Punkten zu Linien. Betrachten wir ein neues Record:<\/p>\n<pre>record Line( Point start, Point end ) {}<\/pre>\n<p>Schreiben wir eine zweite Methode isZeroLine(\u2026), die \u00fcberpr\u00fcft, ob die beiden Punkte der Linie Null sind oder nicht. Beginnen wir mit einer Pattern-Variablen:<\/p>\n<pre>static boolean isZeroLine( Object object ) {\r\n\r\n\u00a0 return\u00a0\u00a0\u00a0 object instanceof Line <strong>line<\/strong>\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &amp;&amp; (\u00a0 line.start().x() | line.start().y()\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0| line.end().x() \u00a0\u00a0| line.end().y() ) == 0;\r\n\r\n}<\/pre>\n<p>Da es sich bei Line um einen Record handelt, kann das Record-Pattern angewendet werden:<\/p>\n<pre>static boolean isZeroLine( Object object ) {\r\n\r\n\u00a0 return\u00a0\u00a0\u00a0 object instanceof <strong>Line( Point start, Point end )<\/strong>\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &amp;&amp; (start.x() | start.y() | end.x() | end.y()) == 0;\r\n\r\n}<\/pre>\n<p>Die Datentypen k\u00f6nnen mit var abgek\u00fcrzt werden:<\/p>\n<pre>static boolean isZeroLine( Object object ) {\r\n\r\n\u00a0 return\u00a0\u00a0\u00a0 object instanceof Line( <strong>var<\/strong> start, <strong>var<\/strong> end )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &amp;&amp; (start.x() | start.y() | end.x() | end.y()) == 0;\r\n\r\n}<\/pre>\n<h4>Geschachtelte Record-Pattern<\/h4>\n<p>Record-Pattern k\u00f6nnen auch verschachtelt werden, um komplexere Strukturen abzubilden:<\/p>\n<pre>static boolean isZeroLine( Object object ) {\r\n\r\n\u00a0 return object instanceof <strong>Line(<\/strong>\r\n\r\n<strong>\u00a0\u00a0\u00a0 Point( int x1, int y1 ), Point( int x2, int y2 )<\/strong>\r\n\r\n<strong>\u00a0\u00a0 )<\/strong> &amp;&amp; (x1 | y1 | x2 | y2) == 0;\r\n\r\n}<\/pre>\n<p>Das Code-Volumen schrumpft nicht, daher ist es Geschmacksache, ob die Variante besser ist.<\/p>\n<h4>Record-Pattern bei switch<\/h4>\n<p>Das Pattern-Matching bei switch ist in Java 21 noch leistungsf\u00e4higer geworden. Erstellen wir eine zweite Methode isZero(Object), die sowohl Punkte als auch Linien \u00fcberpr\u00fcfen kann. L\u00f6sen wir die Aufgabe zuerst wie bekannt mit Pattern-Variablen:<\/p>\n<pre>static boolean isZero( Object o ) {\r\n\r\n\u00a0 return switch ( o ) {\r\n\r\n\u00a0\u00a0\u00a0 case Point p -&gt; (p.x() | p.y()) == 0;\r\n\r\n\u00a0\u00a0\u00a0 case Line l -&gt; (l.start().x() | l.start().y() | l.end().x() | l.end().y()) == 0;\r\n\r\n\u00a0\u00a0\u00a0 default -&gt; false;\r\n\r\n\u00a0 };\r\n\r\n}<\/pre>\n<p>Neben instanceof sind Record-Pattern auch bei switch-case m\u00f6glich. Die Methode kann wie folgt umgeschrieben werden:<\/p>\n<pre>static boolean isZero( Object o ) {\r\n\r\n\u00a0 return switch ( o ) {\r\n\r\n\u00a0\u00a0\u00a0 case <strong>Point( int x, int y )<\/strong> -&gt; (x | y) == 0;\r\n\r\n\u00a0\u00a0\u00a0 case <strong>Line( Point s, Point e )<\/strong> -&gt; isZero( s ) &amp;&amp; isZero( e );\r\n\r\n\u00a0\u00a0\u00a0 default -&gt; false;\r\n\r\n\u00a0 };\r\n\r\n}<\/pre>\n<p>Bei der Linie l\u00e4sst sich das Record-Pattern wieder schachteln:<\/p>\n<pre>static boolean isZero( Object o ) {\r\n\r\n\u00a0 return switch ( o ) {\r\n\r\n\u00a0\u00a0\u00a0 \u2026\r\n\r\n\u00a0\u00a0\u00a0 case Line( <strong>Point( int x1, int y1 ), Point( int x2, int y2 ) <\/strong>)\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0-&gt; (x1 | y1 | x2 | y2) == 0;\r\n\r\n\u00a0\u00a0\u00a0 \u2026\r\n\r\n\u00a0 };\r\n\r\n}<\/pre>\n<p>Allerdings ist auch hier der Code wieder l\u00e4nger.<\/p>\n<h4>Pattern-Matching mit Record-Pattern und when<\/h4>\n<p>Wir haben gesehen, dass Pattern-Matching mit Record-Pattern zur Destrukturierung m\u00f6glich ist. Auch l\u00e4sst sich ein when f\u00fcr eine weitere Abfrage einsetzen. Die Bedingung hinter when kann auf die Pattern-Variable oder Variable aus dem Record-Pattern zugreifen.<\/p>\n<p>Ein Beispiel: Wir m\u00f6chten Candy und Book als Records implementieren:<\/p>\n<pre>record Candy( int calories ) {}\r\n\r\nrecord Book( String title, int numberOfPages ) {}<\/pre>\n<p>Ein Block kann wie folgt Ausgaben zu unterschiedlichen Objekttypen formulieren:<\/p>\n<pre>Object object = new Candy( 120 );\r\n\r\n\r\n\r\n\r\nswitch ( object ) {\r\n\r\n\u00a0 case Candy candy\r\n\r\n\u00a0 when candy.calories &gt; 10_000 -&gt;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 System.out.println( \"Are you trying to sweeten the whole world?\" );\r\n\r\n\r\n\r\n\r\n\u00a0 case Candy candy -&gt;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 System.out.println(\"Is this candy trying to start a dance party in my mouth?\");\r\n\r\n\r\n\r\n\r\n\u00a0 <strong>case Book( var title, var pages )<\/strong>\r\n\r\n<strong>\u00a0 when pages &gt; 100<\/strong> -&gt;\r\n\r\n\u00a0 \u00a0\u00a0\u00a0\u00a0System.out.println(\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\"Looks like someone was on a mission to make the dictionary jealous.\" );\r\n\r\n\r\n\r\n\r\n\u00a0 case Book( var title, var pages )\r\n\r\n\u00a0 when title.isEmpty() -&gt;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 System.out.println(\"Diving into books that forgot to introduce themselves.\");\r\n\r\n\r\n\r\n\r\n\u00a0 case Book -&gt; System.out.println( \"Opening minds, one page at a time\" );\r\n\r\n\r\n\r\n\r\n\u00a0 default -&gt; System.out.println(\"Who knew boredom could be so three-dimensional?\");\r\n\r\n};<\/pre>\n<p>Das Beispiel zeigt Pattern-Matching sowohl ohne als auch mit Record-Pattern. Auch hier m\u00fcssen wir erneut die Dominanz beachten. Es w\u00e4re inkorrekt, case Book -&gt; \u00fcber die anderen Fallbl\u00f6cke case Book( var title, var pages ) when \u2026 zu setzen.<\/p>\n<p><a href=\"#_ftnref1\" name=\"_ftn1\">[1]<\/a> https:\/\/openjdk.org\/jeps\/440<\/p>\n","protected":false},"excerpt":{"rendered":"<p>F\u00fchren wir f\u00fcr die folgenden Beispiele ein Record f\u00fcr Punkte ein: record Point( int x, int y ) {} Nun m\u00f6chten wir \u00fcberpr\u00fcfen, ob die X-Y-Koordinaten eines Punktes auf null stehen. Daf\u00fcr erstellen wir eine Methode namens isZeroPoint(\u2026), die alle Objekttypen akzeptiert und false zur\u00fcckgibt, wenn es sich nicht um einen Punkt handelt: static boolean [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11,124],"tags":[],"class_list":["post-4683","post","type-post","status-publish","format-standard","hentry","category-insel","category-java-21"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4683","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=4683"}],"version-history":[{"count":2,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4683\/revisions"}],"predecessor-version":[{"id":4685,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4683\/revisions\/4685"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=4683"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=4683"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=4683"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}