{"id":1652,"date":"2021-03-14T14:29:32","date_gmt":"2021-03-14T13:29:32","guid":{"rendered":"https:\/\/benjiweber.co.uk\/blog\/?p=1652"},"modified":"2021-03-14T14:29:34","modified_gmt":"2021-03-14T13:29:34","slug":"java-16-pattern-matching-fun","status":"publish","type":"post","link":"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/","title":{"rendered":"Java 16 Pattern Matching Fun"},"content":{"rendered":"\n<p class=\"lead\">Java 16 brings <a href=\"https:\/\/openjdk.java.net\/jeps\/394\">Pattern Matching for instanceof<\/a>. It&#8217;s a feature with exciting possibilities, though quite limited in its initial incarnation.&nbsp;<\/p>\n\n\n\n<h2>Basic Pattern Matching<\/h2>\n\n\n\n<p>We can now do things like&nbsp;<\/p>\n\n\n\n<pre lang=\"java\">Object o = \"hello\";\nif (o instanceof String s) {\n    System.out.println(s.toUpperCase());\n}<\/pre>\n\n\n\n<p>Note the variable &#8220;s&#8221;, which is then used without any casting needed.<\/p>\n\n\n\n<h2 style=\"padding-top:20px;\">Deconstructing Optionals<\/h2>\n\n\n\n<p>It would be even nicer if we could deconstruct structured types at the same time. I think it&#8217;s coming in a future version, but I&#8217;m impatient. Let&#8217;s see how close we can get with the current tools.<\/p>\n\n\n\n<p>One thing we can do is use a function on the left hand side of this expression.<\/p><p> Let&#8217;s consider the example of dealing with unknown values in Java. There&#8217;s a fair amount of legacy to deal with. Sometimes you&#8217;ll get a value, sometimes you&#8217;ll get a null. Sometimes you&#8217;ll get an <span style=\"font-family:monospace\">Optional&lt;T&gt;<\/span> , sometimes you&#8217;ll get an <span style=\"font-family:monospace\">Optional&lt;Optional&lt;T&gt;&gt;<\/span> etc.<\/p>\n\n\n\n<h3 style=\"padding-top: 20px;\">How can we make unknowns nicer to deal with?<\/h3>\n\n\n\n<p>We could create a little utility function that lets us convert all of these forms of unknown into either a value or not. Then match with an instanceof test.<\/p>\n\n\n\n<pre lang=\"java\">Object unknown = Optional.of(\"Hello World\");\nassertEquals(\n       \"hello world\",\n       unwrap(unknown) instanceof String s\n               ? s.toLowerCase()\n               : \"absent\"\n);<\/pre>\n\n\n\n<p>Thanks to instanceof pattern matching we can just use the string directly, without having to resort to passing method references i.e <span style=\"font-family:monospace\">optional.map(String::toLowercase)<\/span>\u00a0<\/p>\n\n\n\n<p>The unwrap utility itself uses pattern matching against Optional to recursively unwrap values from nested optionals. It also converts nulls and <span style=\"font-family:monospace\">Optional.empty()<\/span> to a non-instantiatable type for the purposes of ensuring they can never match the above pattern.<\/p>\n\n\n\n<pre lang=\"java\">static Object unwrap(Object o) {\n   if (o instanceof Optional<?> opt) {\n       return opt.isPresent() ? unwrap(opt.get()) : None;\n   } else if (o != null) {\n       return o;\n   } else {\n       return None;\n   }\n}\nstatic class None {\n   private None() {}\n   public static final None None = new None();\n}<\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/OptionalPatternMatchTest.java\">Here&#8217;s several more examples<\/a>, if you&#8217;d like to explore further.<\/p>\n\n\n\n<h2 style=\"padding-top:20px;\">Deconstructing Records<\/h2>\n\n\n\n<p>What about more complex structures? Now that we have <a href=\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/\">record types<\/a>, wouldn&#8217;t it be great if we can deconstruct them to work with individual components more easily. I think until more powerful type patterns exist in the language we&#8217;ll have to diverge from the <span style=\"font-family: monospace;\">instanceof<\/span> approach.&nbsp;<\/p>\n\n\n\n<p>I previously showed how we could <a href=\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/#!:~:text=Decomposing%20Records\">do this for records we control<\/a>, by having them implement an interface. What about records we do not control? How can we deconstruct those?\u00a0<\/p>\n\n\n\n<p>This is about the closest I can get to what I&#8217;d hope would be possible as a first class citizen in the language in future.<\/p>\n\n\n\n<pre lang=\"java\">record Name(String first, String last) {}\nObject name = new Name(\"Benji\", \"Weber\");\nIf.instance(name, (String first, String last) -> {\n   System.out.println(first.toLowerCase() + last.toLowerCase()); \/\/ prints benjiweber\n});\n<\/pre>\n\n\n\n<p>It takes a record (Name) and a lambda where the method parameters are of the same types as the component types in the record. It deconstructs the record component parts and passes them to the lambda to use (assuming the record really matches).<\/p>\n\n\n\n<p>We could also use as an expression to return a value, as long as we provide a fallback for the case when the pattern does not match.<\/p>\n\n\n\n<pre lang=\"java\">Object zoo = new Zoo(new Duck(\"Quack\"), new Dog(\"Woof\"));\n\nString result = withFallback(\"Fail\").\n    If.instance(zoo, (Duck duck, Dog dog) ->\n       duck.quack() + dog.woof()\n    ); \/\/ result is QuackWoof<\/pre>\n\n\n\n<h3>So how does this work?&nbsp;<\/h3>\n\n\n\n<p><span style=\"font-family:monospace\">If.instance<\/span> is a static method which takes an <span style=\"font-family:monospace\">Object<\/span> of unknown type (we hope it will be a <span style=\"font-family:monospace\">Record<\/span>), and a lambda function that we want to pattern match against the provided object.<\/p>\n\n\n\n<p>How can we use a lambda as a type pattern? We can use the technique from my <a href=\"https:\/\/benjiweber.co.uk\/blog\/2015\/08\/04\/lambda-type-references\/\">lambda type references<\/a> article\u2014have the lambda type be a <a href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/api\/java\/lang\/invoke\/SerializedLambda.html\">SerializableLambda<\/a> which will allow us to use reflection to read the types of each parameter.&nbsp;<\/p>\n\n\n\n<pre lang=\"java\">static <T,U,V> void instance(Object o, MethodAwareTriConsumer<T,U,V> action) { \n\n}<\/pre>\n\n\n\n<p>So we start with something like the above, a method taking an object and a reflectable lambda function.<\/p>\n\n\n\n<p>Next we can make use of pattern matching again to check if it&#8217;s a record.<\/p>\n\n\n\n<pre lang=\"java\">static <T,U,V> void instance(Object o, MethodAwareTriConsumer<T,U,V> action) {\n   if (o instanceof Record r) {\n\t\/\/ now we know it's a record\n   }\n}<\/pre>\n\n\n\n<p>Records allow reflection on their component parts. Let&#8217;s check whether we have enough component parts to match the pattern.<\/p>\n\n\n\n<pre lang=\"java\">static <T,U,V> void instance(Object o, MethodAwareTriConsumer<T,U,V> action) {\n   if (o instanceof Record r) {\n       if (r.getClass().getRecordComponents().length < 3) {\n           return;\n       }\n       \n\t \/\/ at this point we have a record with enough components and can use them.\n   }\n}<\/pre>\n\n\n\n<p>Now we can invoke the passed action itself&nbsp;<\/p>\n\n\n\n<pre lang=\"java\">action.tryAccept((T) nthComponent(0, r), (U) nthComponent(1, r), (V) nthComponent(2, r));<\/pre>\n\n\n\n<p>Where <span style=\"font-family:monospace\">nthComponent<\/span> uses reflection to access the relevant component property of the record.<\/p>\n\n\n\n<pre lang=\"java\">private static Object nthComponent(int n, Record r)  {\n   try {\n       return r.getClass().getRecordComponents()[n].getAccessor().invoke(r);\n   } catch (Exception e) {\n       throw new RuntimeException(e);\n   }\n}<\/pre>\n\n\n\n<p><span style=\"font-family:monospace\">tryAccept<\/span> is a helper default method I've added in <span style=\"font-family:monospace\">MethodAwareTriConsumer<\/span>. It checks whether the types of the provided values match the method signature before trying to pass them. Avoiding <span style=\"font-family:monospace\">ClassCastException<\/span>s&nbsp;<\/p>\n\n\n\n<pre lang=\"java\">interface MethodAwareTriConsumer<T,U,V> extends TriConsumer<T,U,V>, ParamTypeAware {\n   default void tryAccept(T one, U two, V three) {\n       if (acceptsTypes(one, two, three)) {\n           accept(one, two, three);\n       }\n   }\n   default boolean acceptsTypes(Object one, Object two, Object three) {\n       return paramType(0).isAssignableFrom(one.getClass())\n               && paramType(1).isAssignableFrom(two.getClass())\n               && paramType(2).isAssignableFrom(three.getClass());\n   }\n  \n   default Class<?> paramType(int n) {\n       int actualParameters = method().getParameters().length; \/\/ captured final variables may be prepended\n       int expectedParameters = 3;\n       return method().getParameters()[(actualParameters - expectedParameters) + n].getType();\n   }\n}<\/pre>\n\n\n\n<p>Then put all this together and we can pattern match against Objects of unknown type, and deconstruct them if they're records matching the provided lambda type-pattern.<\/p>\n\n\n\n<pre lang=\"java\">record Colour(Integer r, Integer g, Integer b) {}\n\nObject unknown = new Colour(5,6,7); \/\/ note the Object type\n\nint result = withFallback(-1).\n    If.instance(unknown, (Integer r, Integer g, Integer b) ->\n       r + g + b\n    );\n\nassertEquals(18, result);<\/pre>\n\n\n\n<p>Degrading safely if the pattern does not match<\/p>\n\n\n\n<pre lang=\"java\">Object unknown = new Name(\"benji\", \"weber\");\n\nint result = withFallback(-1).\n    If.instance(unknown, (Integer r, Integer g, Integer b) ->\n       r + g + b\n    );\n\nassertEquals(-1, result);<\/pre>\n\n\n\n<p>Code for the record deconstruction and several more examples all <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/DecomposeRecordsTest.java\">in this test on github<\/a>. Hopefully all this will be made redundant by future enhancements to Java's type patterns :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Java 16 brings Pattern Matching for instanceof. It&#8217;s a feature with exciting possibilities, though quite limited in its initial incarnation.&nbsp; Basic Pattern Matching We can now do things like&nbsp; Object o = &#8220;hello&#8221;; if (o instanceof String s) { System.out.println(s.toUpperCase()); } Note the variable &#8220;s&#8221;, which is then used without any casting needed. Deconstructing Optionals&#8230;  <a href=\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/\" class=\"more-link\" title=\"Read Java 16 Pattern Matching Fun\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":1695,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[8],"tags":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<meta name=\"description\" content=\"Deconstructing Optionals or Record types with Java 16 Pattern Matching.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Java 16 Pattern Matching Fun - Benji&#039;s Blog\" \/>\n<meta property=\"og:description\" content=\"Deconstructing Optionals or Record types with Java 16 Pattern Matching.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/\" \/>\n<meta property=\"og:site_name\" content=\"Benji&#039;s Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-03-14T13:29:32+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-03-14T13:29:34+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/benjiweber.co.uk\/blog\/wp-content\/uploads\/2021\/03\/dinstance9.png\" \/>\n\t<meta property=\"og:image:width\" content=\"765\" \/>\n\t<meta property=\"og:image:height\" content=\"383\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#website\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/\",\"name\":\"Benji&#039;s Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/benjiweber.co.uk\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/wp-content\/uploads\/2021\/03\/dinstance9.png\",\"width\":765,\"height\":383},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/#webpage\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/\",\"name\":\"Java 16 Pattern Matching Fun - Benji&#039;s Blog\",\"isPartOf\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/#primaryimage\"},\"datePublished\":\"2021-03-14T13:29:32+00:00\",\"dateModified\":\"2021-03-14T13:29:34+00:00\",\"author\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#\/schema\/person\/45ecb36b51f4ce99e6929d2d31ca5c09\"},\"description\":\"Deconstructing Optionals or Record types with Java 16 Pattern Matching.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/benjiweber.co.uk\/blog\/2021\/03\/14\/java-16-pattern-matching-fun\/\"]}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#\/schema\/person\/45ecb36b51f4ce99e6929d2d31ca5c09\",\"name\":\"benji\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/05fb47b31a0b329e1b790074a9b624ef?s=96&d=mm&r=g\",\"caption\":\"benji\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","amp_enabled":false,"_links":{"self":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/1652"}],"collection":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/comments?post=1652"}],"version-history":[{"count":47,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/1652\/revisions"}],"predecessor-version":[{"id":1700,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/1652\/revisions\/1700"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/media\/1695"}],"wp:attachment":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=1652"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=1652"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=1652"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}