{"id":1479,"date":"2020-09-19T20:46:22","date_gmt":"2020-09-19T19:46:22","guid":{"rendered":"https:\/\/benjiweber.co.uk\/blog\/?p=1479"},"modified":"2022-06-10T18:05:06","modified_gmt":"2022-06-10T17:05:06","slug":"fun-with-java-records","status":"publish","type":"post","link":"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/","title":{"rendered":"Fun with Java Records"},"content":{"rendered":"\n<p class=\"lead\">A while back I promised to follow up from this tweet to elaborate on the fun I was having with Java&#8217;s new <a href=\"https:\/\/openjdk.java.net\/jeps\/384\">Records<\/a> (currently preview) feature.<\/p>\n\n\n\n<figure class=\"wp-block-embed-twitter wp-block-embed is-type-rich is-provider-twitter\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"twitter-tweet\" data-width=\"550\" data-dnt=\"true\"><p lang=\"en\" dir=\"ltr\">Oh, and because records can implicitly implement interfaces they&#39;re quite handy for mixins<a href=\"https:\/\/t.co\/LJdBIMQkv7\">https:\/\/t.co\/LJdBIMQkv7<\/a> <a href=\"https:\/\/t.co\/HKrcItQZh5\">pic.twitter.com\/HKrcItQZh5<\/a><\/p>&mdash; Benji Weber (@benjiweber) <a href=\"https:\/\/twitter.com\/benjiweber\/status\/1272077666263457792?ref_src=twsrc%5Etfw\">June 14, 2020<\/a><\/blockquote><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script>\n<\/div><\/figure>\n\n\n\n<p>Records, like lambdas and default methods on interfaces are tremendously useful language features because they enable many different patterns and uses beyond the obvious.<\/p>\n\n\n\n<p>Java 8 brought lambdas, with lots of compelling uses for streams. What I found <a href=\"https:\/\/www.youtube.com\/watch?v=-3SJYI1XHm0\">exciting at the time<\/a> was that for the first time lots of things that we&#8217;d previously have to have waited for as new language features could become library features. While waiting for lambdas we had a Java 7 release with <a href=\"https:\/\/docs.oracle.com\/javase\/tutorial\/essential\/exceptions\/tryResourceClose.html\">try-with-resources<\/a>. If we&#8217;d had lambdas we could have implemented something similar in a library without needing a language change. <\/p>\n\n\n\n<p>There&#8217;s often lots one can do with a bit of creativity. Even if Brian Goetz does sometimes spoil one&#8217;s fun \u00ac_\u00ac<\/p>\n\n\n\n<figure class=\"wp-block-embed-twitter wp-block-embed is-type-rich is-provider-twitter\"><div class=\"wp-block-embed__wrapper\">\nhttps:\/\/twitter.com\/nipafx\/status\/1028979167591890944\n<\/div><\/figure>\n\n\n\n<p>Records are another such exciting addition to Java. They provide a missing feature that&#8217;s hard to correct for in libraries due to sensible limitations on other features (e.g. default methods on interfaces not being able to override equals\/hashcode)<\/p>\n\n\n\n<p>Here&#8217;s a few things that records help us do that would otherwise wait indefinitely to appear in the core language.<\/p>\n\n\n\n<h2><br>Implicitly Implement (Forwarding) Interfaces<\/h2>\n\n\n\n<p>Java 8 gave us default methods on interfaces. These allowed us to mix together behaviour defined in multiple interfaces. One use of this is to avoid having to re-implement all of a large interface if you just want to add a new method to an existing type. For example, adding a .map(f) method to List. I called this the <a href=\"https:\/\/benjiweber.co.uk\/blog\/2014\/04\/14\/java-forwarding-interface-pattern\/\">Forwarding Interface<\/a> pattern.<\/p>\n\n\n\n<p>Using forwarding interface still left us with a fair amount of boilerplate, just to delegate to a concrete implementation. Here&#8217;s a MappableList definition using a ForwardingList.<\/p>\n\n\n\n<pre lang=\"java\">class MappableList<T> implements List<T>, ForwardingList<T>, Mappable<T> {\n   private List<T> impl;\n\n   public MappableList(List<T> impl) {\n       this.impl = impl;\n   }\n\n   @Override\n   public List<T> impl() {\n       return impl;\n   }\n}<\/pre>\n\n\n\n<p>The map(f) implementation is defined in Mappable&lt;T&gt; and the List&lt;T&gt; implementation is defined in ForwardingList&lt;T&gt;. All the body of MappableList&lt;T&gt; is boilerplate to delegate to a given List&lt;T&gt; implementation.<\/p>\n\n\n\n<p>We can improve on this a bit using anonymous types thanks to Jdk 10&#8217;s <code>var<\/code>. We don&#8217;t have to define MappableList&lt;T&gt; at all. We can define it inline with intersection casts and structural equivalence with a lambda that returns the delegate type.<\/p>\n\n\n\n<pre lang=\"java\">var y = (IsA<List<String>> & Mappable<String> & FlatMappable<String> & Joinable<String>)\n    () -> List.of(\"Anonymous\", \"Types\");<\/pre>\n\n\n\n<p><a href=\"https:\/\/gist.github.com\/benjiman\/a8945f378691f4c1d258a12bed825ec2#file-example-java-L21\">Full implementation<\/a><\/p>\n\n\n\n<p>This is probably a bit obscure for most people. Intersection casts aren&#8217;t commonly used. You&#8217;d also have to define your desired &#8220;mix&#8221; of behaviours at each usage site.<\/p>\n\n\n\n<p>Records give us a better option. The implementation of a record definition can implicitly implement the boilerplate in the above MappableList definition<\/p>\n\n\n\n<pre lang=\"java\">public record EnhancedList<T>(List<T> inner) implements\n       ForwardingList<T>,\n       Mappable<T>,\n       Filterable<T, EnhancedList<T>>,\n       Groupable<T> {}\n\ninterface ForwardingList<T> extends List<T>, Forwarding<List<T>> {\n   List<T> inner();\n   \/\/\u2026\n}<\/pre>\n\n\n\n<p>Here we have defined a record with a single field named &#8220;<strong>inner<\/strong>&#8220;. This automatically defines a getter called <strong>inner<\/strong>() which implicitly implements the <strong>inner<\/strong>() method on ForwardingList. None of the boilerplate on the above MappableList is needed. Here&#8217;s the <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordMixinsTest.java#L68\">full code<\/a>. Here&#8217;s an <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordMixinsTest.java#L21\">example using it<\/a> to map over a list.<\/p>\n\n\n\n<h2>Decomposing Records<\/h2>\n\n\n\n<p>Let&#8217;s define a Colour record<\/p>\n\n\n\n<pre lang=\"java\">public record Colour(int red, int green, int blue) {}<\/pre>\n\n\n\n<p>This is nice and concise. However, what if we want to get the constituent parts back out again.<\/p>\n\n\n\n<pre lang=\"java\">Colour colour = new Colour(1,2,3);\nvar r = colour.red();\nvar g = colour.green();\nvar b = colour.blue();\nassertEquals(1, r.intValue());\nassertEquals(2, g.intValue());\nassertEquals(3, b.intValue());<\/pre>\n\n\n\n<p>Can we do better? How close can we get to <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Destructuring_assignment#Object_destructuring\">object destructuring<\/a>?<\/p>\n\n\n\n<p>How about this.<\/p>\n\n\n\n<pre lang=\"java\">Colour colour = new Colour(1,2,3);\n\ncolour.decompose((r,g,b) -&gt; {\n   assertEquals(1, r.intValue());\n   assertEquals(2, g.intValue());\n   assertEquals(3, b.intValue());\n});<\/pre>\n\n\n\n<p>How can we implement this in a way that requires minimal boilerplate? Default methods on interfaces come to the rescue again. What if we could get any of this additional sugary goodness on any record, simply by implementing an interface.<\/p>\n\n\n\n<pre lang=\"java\">public record Colour(int red, int green, int blue) \n   implements TriTuple<Colour,Integer,Integer,Integer> {}\n<\/pre>\n\n\n\n<p>Here we&#8217;re making our Colour record implement an interface so it can inherit behaviour from that interface.<\/p>\n\n\n\n<p>Let&#8217;s make it work\u2026<\/p>\n\n\n\n<p>We&#8217;re passing the decompose method a lambda function that accepts three values. We want the implementation to invoke the lambda and pass our constituent values in the record (red, green, blue) as arguments when invoked.<\/p>\n\n\n\n<p>Firstly let&#8217;s declare a default method in our TriTuple interface that accepts a lambda with the right signature.<\/p>\n\n\n\n<pre lang=\"java\">interface TriTuple<TRecord extends Record &#038; TriTuple<TRecord, T, U, V>,T,U,V>\n    default void decompose(TriConsumer<T,U,V> withComponents) {\n   \t\/\/\n    }\n}<\/pre>\n\n\n\n<p>Next we need a way of extracting the component parts of the record. Fortunately Java allows for this. There&#8217;s a new method <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/15\/docs\/api\/java.base\/java\/lang\/Class.html#getRecordComponents()\">Class::getRecordComponents<\/a> that gives us an array of the constituent parts.<\/p>\n\n\n\n<p>This lets us extract each of the three parts of the record and pass to the lambda.<\/p>\n\n\n\n<pre lang=\"java\">var components = this.getClass().getRecordComponents();\nreturn withComponents.apply(\n    (T) components[0].getAccessor().invoke(this),\n    (U) components[1].getAccessor().invoke(this),\n    (V) components[2].getAccessor().invoke(this)\n);\n<\/pre>\n\n\n\n<p>There&#8217;s some tidying we can do, but the above works. A <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java#L106\" data-type=\"URL\" data-id=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java#L106\">very similar implementation<\/a> would allow us to return a result built with the component parts of the record as well.<\/p>\n\n\n\n<pre lang=\"java\">Colour colour = new Colour(1,2,3);\nvar sum = colour.decomposeTo((r,g,b) -&gt; r+g+b);\nassertEquals(6, sum.intValue());<\/pre>\n\n\n\n<h2>Structural Conversion <\/h2>\n\n\n\n<iframe loading=\"lazy\" src=\"https:\/\/www.google.com\/maps\/embed?pb=!4v1600510587912!6m8!1m7!1sW-FrnbHXUp11TcmUwD5CfQ!2m2!1d34.948682659586!2d-119.6916109744277!3f228.54!4f-6.040000000000006!5f3.325193203789971\" width=\"600\" height=\"450\" frameborder=\"0\" style=\"border:0;\" allowfullscreen=\"\" aria-hidden=\"false\" tabindex=\"0\"><\/iframe>\n\n\n\n<p>Sometimes the types get in the way of people doing what they want to do with the data. However wrong it may be \u00ac_\u00ac<\/p>\n\n\n\n<p>Let&#8217;s see if we can allow people to convert between Colours and Towns<\/p>\n\n\n\n<pre lang=\"java\">public record Person(String name, int age, double height) \n    implements TriTuple<Person, String, Integer, Double> {}\npublic record Town(int population, int altitude, int established)\n    implements TriTuple<Town, Integer, Integer, Integer> { }\n\n\nColour colour = new Colour(1, 2, 3);\nTown town = colour.to(Town::new);\nassertEquals(1, town.population());\nassertEquals(2, town.altitude());\nassertEquals(3, town.established());<\/pre>\n\n\n\n<p>How to implement the &#8220;to(..)&#8221; method? We&#8217;ve already done it! It&#8217;s accepting a method reference to Town&#8217;s constructor. This is the same signature and implementation of our decomposeTo method above. So we can just alias it.<\/p>\n\n\n\n<pre lang=\"java\">default <R extends Record &#038; TriTuple<R, T, U, V>> R to(TriFunction<T, U, V, R> ctor) {\n   return decomposeTo(ctor);\n}<\/pre>\n\n\n\n<h2 id=\"replace_property\">Replace Property<\/h2>\n\n\n\n<p>We&#8217;ve now got a nice TriTuple utility interface allowing us to extend the capabilities that tri-records have.<\/p>\n\n\n\n<p>Another nice feature would be to create a new record with just one property changed. Imagine we&#8217;re mixing paint and we want a variant on an existing shade. We could just add more of one colour, not start from scratch.<\/p>\n\n\n\n<pre lang=\"java\">Colour colour = new Colour(1,2,3);\nColour changed = colour.with(Colour::red, 5);\nassertEquals(new Colour(5,2,3), changed);<\/pre>\n\n\n\n<p>We&#8217;re passing the .with(..) method a method reference to the property we want to change, as well as the new value. How can we implement .with(..) ? How can it know that the passed method reference refers to the first component value?<\/p>\n\n\n\n<p>We can in fact match by name.<\/p>\n\n\n\n<p>The <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/15\/docs\/api\/java.base\/java\/lang\/reflect\/RecordComponent.html\">RecordComponent<\/a> type from the standard library that we used above <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/15\/docs\/api\/java.base\/java\/lang\/reflect\/RecordComponent.html#getName()\">can give us the name<\/a> of each component of the record.<\/p>\n\n\n\n<p>We can get the name of the passed method reference by using a functional interface that extends from Serializable. This lets us access the name of the method the lambda is invoking. In this case giving us back the name &#8220;red&#8221;<\/p>\n\n\n\n<pre lang=\"java\">default <R> TRecord with(MethodAwareFunction<TRecord, R> prop, R newValue) { \n    \/\/\n}<\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java#L226\">MethodAwareFunction<\/a> extends another utility interface <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java#L196\">MethodFinder<\/a> which provides us access to the <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/15\/docs\/api\/java.base\/java\/lang\/reflect\/Method.html\">Method<\/a> invoked and from there, the name.<\/p>\n\n\n\n<p>The last challenge is reflectively accessing the constructor of the type we&#8217;re trying to create. Fortunately we&#8217;re passing the type information to our utility interface at declaration time<\/p>\n\n\n\n<pre lang=\"java\">public record Colour(int red, int green, int blue)\n    implements TriTuple<Colour,Integer,Integer,Integer> {}<\/pre>\n\n\n\n<p>We want the <span class=\"inline-code\">Colour<\/span> constructor. We can get it from <span class=\"inline-code\">Colour.class<\/span>. We can get this by reflectively accessing the first type parameter of the <span class=\"inline-code\">TriTuple<\/span> interface. Using <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/15\/docs\/api\/java.base\/java\/lang\/Class.html#getGenericInterfaces()\">Class::getGenericInterfaces()<\/a> then <a href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/api\/java\/lang\/reflect\/ParameterizedType.html#getActualTypeArguments--\">ParameterizedType::getActualTypeArguments()<\/a> and taking the first to get a <span class=\"inline-code\">Class&lt;Colour&gt;<\/span><\/p>\n\n\n\n<p>Here&#8217;s a <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java#L123\">full implementation.<\/a><\/p>\n\n\n\n<h2>Automatic Builders<\/h2>\n\n\n\n<p>We can extend the above to have some similarities with the builder pattern, without having to create a builder manually each time.<\/p>\n\n\n\n<p>We&#8217;ve already got our .with(namedProperty, value) method to build a record step by step. All we need is a way of creating a record with default values that we can replace with our desired values one at a time.<\/p>\n\n\n\n<pre lang=\"java\">Person sam = builder(Person::new)\n   .with(Person::name, \"Sam\")\n   .with(Person::age, 34)\n   .with(Person::height, 83.2);\n\nassertEquals(new Person(\"Sam\", 34, 83.2), sam);\n\nstatic <T, U, V, TBuild extends Record &#038; TriTuple<TBuild, T, U ,V>> TBuild builder(Class<TBuild> cls) {\n    \/\/\n}<\/pre>\n\n\n\n<p>This static builder method invokes the passed constructor reference passing it appropriate default values. We&#8217;ll use the same SerializedLambda technique from above to access the appropriate argument types.<\/p>\n\n\n\n<pre lang=\"java\">static <T, U, V, TBuild extends Record &#038; TriTuple<TBuild, T, U ,V>> TBuild builder(MethodAwareTriFunction<T,U,V,TBuild> ctor) {\n   var reflectedConstructor = ctor.getContainingClass().getConstructors()[0];\n   var defaultConstructorValues = Stream.of(reflectedConstructor.getParameterTypes())\n           .map(defaultValues::get)\n           .collect(toList());\n   return ctor.apply(\n       (T)defaultConstructorValues.get(0),\n       (U)defaultConstructorValues.get(1),\n       (V)defaultConstructorValues.get(2)\n   );\n}<\/pre>\n\n\n\n<p>Once we&#8217;ve invoked the constructor with default values we can re-use the .with(prop,value) method we created above to build a record up one value at a time.<\/p>\n\n\n\n<h2>Example Usage<\/h2>\n\n\n\n<pre lang=\"java\">public record Colour(int red, int green, int blue) \n    implements TriTuple<Colour,Integer,Integer,Integer> {}\n\npublic record Person(String name, int age, double height) \n    implements TriTuple<Person, String, Integer, Double> {}\n\npublic record Town(int population, int altitude, int established) \n    implements TriTuple<Town, Integer, Integer, Integer> {}\n\npublic record EnhancedList<T>(List<T> inner) implements\n    ForwardingList<T>,\n    Mappable<T> {}\n\n@Test\npublic void map() {\n    var mappable = new EnhancedList<>(List.of(\"one\", \"two\"));\n\n    assertEquals(\n        List.of(\"oneone\", \"twotwo\"),\n        mappable.map(s -> s + s)\n    );\n}\n\n@Test\npublic void decomposable_record() {\n   Colour colour = new Colour(1,2,3);\n\n   colour.decompose((r,g,b) -> {\n       assertEquals(1, r.intValue());\n       assertEquals(2, g.intValue());\n       assertEquals(3, b.intValue());\n   });\n\n   var sum = colour.decomposeTo((r,g,b) -> r+g+b);\n   assertEquals(6, sum.intValue());\n}\n\n@Test\npublic void structural_convert() {\n   Colour colour = new Colour(1, 2, 3);\n   Town town = colour.to(Town::new);\n   assertEquals(1, town.population());\n   assertEquals(2, town.altitude());\n   assertEquals(3, town.established());\n}\n\n@Test\npublic void replace_property() {\n   Colour colour = new Colour(1,2,3);\n   Colour changed = colour.with(Colour::red, 5);\n   assertEquals(new Colour(5,2,3), changed);\n\n   Person p1 = new Person(\"Leslie\", 12, 48.3);\n   Person p2 = p1.with(Person::name, \"Beverly\");\n   assertEquals(new Person(\"Beverly\", 12, 48.3), p2);\n}\n\n@Test\npublic void auto_builders() {\n   Person sam = builder(Person::new)\n           .with(Person::name, \"Sam\")\n           .with(Person::age, 34)\n           .with(Person::height, 83.2);\n\n   assertEquals(new Person(\"Sam\", 34, 83.2), sam);\n}<\/pre>\n\n\n\n<p>Code is all in <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordTuplesTest.java\">this test<\/a> and <a href=\"https:\/\/github.com\/benjiman\/recordmixins\/blob\/master\/src\/test\/java\/com\/benjiweber\/recordmixins\/RecordMixinsTest.java\">this other test<\/a>. Supporting records with arities other than 3 is left as an exercise to the reader \u00ac_\u00ac<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while back I promised to follow up from this tweet to elaborate on the fun I was having with Java&#8217;s new Records (currently preview) feature. Records, like lambdas and default methods on interfaces are tremendously useful language features because they enable many different patterns and uses beyond the obvious. Java 8 brought lambdas, with&#8230;  <a href=\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/\" class=\"more-link\" title=\"Read Fun with Java Records\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"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=\"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\/2020\/09\/19\/fun-with-java-records\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Fun with Java Records - Benji&#039;s Blog\" \/>\n<meta property=\"og:description\" content=\"A while back I promised to follow up from this tweet to elaborate on the fun I was having with Java&#8217;s new Records (currently preview) feature. Records, like lambdas and default methods on interfaces are tremendously useful language features because they enable many different patterns and uses beyond the obvious. Java 8 brought lambdas, with... Read more &raquo;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/\" \/>\n<meta property=\"og:site_name\" content=\"Benji&#039;s Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-09-19T19:46:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-06-10T17:05:06+00:00\" \/>\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\":\"WebPage\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/#webpage\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/\",\"name\":\"Fun with Java Records - Benji&#039;s Blog\",\"isPartOf\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#website\"},\"datePublished\":\"2020-09-19T19:46:22+00:00\",\"dateModified\":\"2022-06-10T17:05:06+00:00\",\"author\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#\/schema\/person\/45ecb36b51f4ce99e6929d2d31ca5c09\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/benjiweber.co.uk\/blog\/2020\/09\/19\/fun-with-java-records\/\"]}]},{\"@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\/1479"}],"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=1479"}],"version-history":[{"count":27,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/1479\/revisions"}],"predecessor-version":[{"id":2242,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/1479\/revisions\/2242"}],"wp:attachment":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=1479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=1479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=1479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}