<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"Brian Goetz" <brian.goetz@oracle.com><br><b>To: </b>"David Alayachew" <davidalayachew@gmail.com>, "amber-dev" <amber-dev@openjdk.org><br><b>Sent: </b>Thursday, September 21, 2023 7:18:13 PM<br><b>Subject: </b>Re: Question about records and overriding their "magic methods"<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left: 2px solid #1010FF; margin-left: 5px; padding-left: 5px; color: #000; font-weight: normal; font-style: normal; text-decoration: none; font-family: Helvetica,Arial,sans-serif; font-size: 12pt;" data-mce-style="border-left: 2px solid #1010FF; margin-left: 5px; padding-left: 5px; color: #000; font-weight: normal; font-style: normal; text-decoration: none; font-family: Helvetica,Arial,sans-serif; font-size: 12pt;"><span style="font-size: large;" data-mce-style="font-size: large;"><span style="font-family: monospace;" data-mce-style="font-family: monospace;">We explored this topic somewhat when designing the feature.  <br><br>When you override a method x, you can still delegate (via super.x()) to the thing you override.  We considered doing the same here (delegating to default.x()), but concluded that this failed the power-to-weight-ratio test, because usually you do not want to _refine_ an equals/toString/hashCode calculation, but instead broaden it (such as comparing arrays by contents rather than by ==.)  <br><br>If you pull on this "string" a bit, the API that you would want here is complex and enormous, and has as many tuning knobs as a Lombok.  So refactoring records that customize Object methods becomes a higher-responsibility activity.  Leave "future you" some comments for what you were thinking today.</span></span></blockquote><div><br></div><div><br data-mce-bogus="1"></div><div>If you really really want to do it, you can use the bootstrap method in java.lang.runtime.ObjectsMethods but it's ugly.<br></div><div><br></div><div><div style="background-color: #ffffff; color: #080808;" data-mce-style="background-color: #ffffff; color: #080808;"><div style="background-color: #ffffff; color: #080808;" data-mce-style="background-color: #ffffff; color: #080808;"><div style="background-color: #ffffff; color: #080808;" data-mce-style="background-color: #ffffff; color: #080808;"><pre style="font-family: 'JetBrains Mono',monospace; font-size: 9.8pt;" data-mce-style="font-family: 'JetBrains Mono',monospace; font-size: 9.8pt;"><span style="color: #0033b3;" data-mce-style="color: #0033b3;">public class </span><span style="color: #000000;" data-mce-style="color: #000000;">CallingRecordToStringDemo </span>{<br><br>  <span style="color: #0033b3;" data-mce-style="color: #0033b3;">record </span><span style="color: #000000;" data-mce-style="color: #000000;">Foo</span>(<span style="color: #000000;" data-mce-style="color: #000000;">String </span>blah, <span style="color: #0033b3;" data-mce-style="color: #0033b3;">int </span>value) {<br>    <span style="color: #0033b3;" data-mce-style="color: #0033b3;">private static final </span><span style="color: #000000;" data-mce-style="color: #000000;">MethodHandle </span><span style="color: #871094; font-style: italic;" data-mce-style="color: #871094; font-style: italic;">TO_STRING</span>;<br>    <span style="color: #0033b3;" data-mce-style="color: #0033b3;">static </span>{<br>      <span style="color: #0033b3;" data-mce-style="color: #0033b3;">var </span><span style="color: #000000;" data-mce-style="color: #000000;">lookup </span>= <span style="color: #000000;" data-mce-style="color: #000000;">MethodHandles</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">lookup</span>();<br>      <span style="color: #0033b3;" data-mce-style="color: #0033b3;">var </span><span style="color: #000000;" data-mce-style="color: #000000;">components </span>= <span style="color: #000000;" data-mce-style="color: #000000;">Foo</span>.<span style="color: #0033b3;" data-mce-style="color: #0033b3;">class</span>.getRecordComponents();<br>      <span style="color: #000000;" data-mce-style="color: #000000;">CallSite callsite</span>;<br>      <span style="color: #0033b3;" data-mce-style="color: #0033b3;">try </span>{<br>        <span style="color: #000000;" data-mce-style="color: #000000;">callsite </span>= (<span style="color: #000000;" data-mce-style="color: #000000;">CallSite</span>) <span style="color: #000000;" data-mce-style="color: #000000;">ObjectMethods</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">bootstrap</span>(<span style="color: #000000;" data-mce-style="color: #000000;">lookup</span>,<br>            <span style="color: #067d17;" data-mce-style="color: #067d17;">"toString"</span>,<br>            <span style="color: #000000;" data-mce-style="color: #000000;">MethodType</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">methodType</span>(<span style="color: #000000;" data-mce-style="color: #000000;">String</span>.<span style="color: #0033b3;" data-mce-style="color: #0033b3;">class</span>, <span style="color: #000000;" data-mce-style="color: #000000;">Foo</span>.<span style="color: #0033b3;" data-mce-style="color: #0033b3;">class</span>),<br>            <span style="color: #000000;" data-mce-style="color: #000000;">Foo</span>.<span style="color: #0033b3;" data-mce-style="color: #0033b3;">class</span>,<br>            <span style="color: #000000;" data-mce-style="color: #000000;">Arrays</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">stream</span>(<span style="color: #000000;" data-mce-style="color: #000000;">components</span>).map(<span style="color: #000000;" data-mce-style="color: #000000;">RecordComponent</span>::getName).collect(<span style="color: #000000;" data-mce-style="color: #000000;">Collectors</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">joining</span>(<span style="color: #067d17;" data-mce-style="color: #067d17;">";"</span>)),<br>            <span style="color: #000000;" data-mce-style="color: #000000;">Arrays</span>.<span style="font-style: italic;" data-mce-style="font-style: italic;">stream</span>(<span style="color: #000000;" data-mce-style="color: #000000;">components</span>).map(c -> {<br>              <span style="color: #0033b3;" data-mce-style="color: #0033b3;">try </span>{<br>                <span style="color: #0033b3;" data-mce-style="color: #0033b3;">return </span><span style="color: #851691;" data-mce-style="color: #851691;">lookup</span>.unreflect(c.getAccessor());<br>              } <span style="color: #0033b3;" data-mce-style="color: #0033b3;">catch </span>(<span style="color: #000000;" data-mce-style="color: #000000;">IllegalAccessException </span>e) {<br>                <span style="color: #0033b3;" data-mce-style="color: #0033b3;">throw new </span>AssertionError(e);<br>              }<br>            }).toArray(<span style="color: #000000;" data-mce-style="color: #000000;">MethodHandle</span>[]::<span style="color: #0033b3;" data-mce-style="color: #0033b3;">new</span>));<br>      } <span style="color: #0033b3;" data-mce-style="color: #0033b3;">catch </span>(<span style="color: #000000;" data-mce-style="color: #000000;">Throwable </span>e) {<br>        <span style="color: #0033b3;" data-mce-style="color: #0033b3;">throw new </span>AssertionError(e);<br>      }<br>      <span style="color: #871094; font-style: italic;" data-mce-style="color: #871094; font-style: italic;">TO_STRING </span>= <span style="color: #000000;" data-mce-style="color: #000000;">callsite</span>.dynamicInvoker();<br>    }<br><br>    <span style="color: #9e880d;" data-mce-style="color: #9e880d;">@Override<br></span><span style="color: #9e880d;" data-mce-style="color: #9e880d;">    </span><span style="color: #0033b3;" data-mce-style="color: #0033b3;">public </span><span style="color: #000000;" data-mce-style="color: #000000;">String </span><span style="color: #00627a;" data-mce-style="color: #00627a;">toString</span>() {<br>      <span style="color: #0033b3;" data-mce-style="color: #0033b3;">try </span>{<br>        <span style="color: #0033b3;" data-mce-style="color: #0033b3;">return </span>(<span style="color: #000000;" data-mce-style="color: #000000;">String</span>) <span style="color: #871094; font-style: italic;" data-mce-style="color: #871094; font-style: italic;">TO_STRING</span>.invokeExact(<span style="color: #0033b3;" data-mce-style="color: #0033b3;">this</span>);<br>      } <span style="color: #0033b3;" data-mce-style="color: #0033b3;">catch </span>(<span style="color: #000000;" data-mce-style="color: #000000;">Error </span>e) {<br>        <span style="color: #0033b3;" data-mce-style="color: #0033b3;">throw </span>e;<br>      } <span style="color: #0033b3;" data-mce-style="color: #0033b3;">catch </span>(<span style="color: #000000;" data-mce-style="color: #000000;">Throwable </span>e) {<br>        <span style="color: #0033b3;" data-mce-style="color: #0033b3;">throw new </span>AssertionError(e);<br>      }<br>    }<br>  }<br><br>  <span style="color: #0033b3;" data-mce-style="color: #0033b3;">public static void </span><span style="color: #00627a;" data-mce-style="color: #00627a;">main</span>(<span style="color: #000000;" data-mce-style="color: #000000;">String</span>[] args) {<br>    <span style="color: #000000;" data-mce-style="color: #000000;">System</span>.<span style="color: #871094; font-style: italic;" data-mce-style="color: #871094; font-style: italic;">out</span>.println(<span style="color: #0033b3;" data-mce-style="color: #0033b3;">new </span>Foo(<span style="color: #067d17;" data-mce-style="color: #067d17;">"blah"</span>, <span style="color: #1750eb;" data-mce-style="color: #1750eb;">42</span>));<br>  }<br>}</pre></div></div></div></div><div><br></div><div>We also do not want to close the door to supporting super.x() if at some point in the future if the VM spec is changed to introduce a way to generate bridge methods at runtime.</div><div>The record methods toString/equals/hashCode will be declare as abstract bridge method in that case.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>regards,</div><div>RĂ©mi</div><div><br></div><blockquote style="border-left: 2px solid #1010FF; margin-left: 5px; padding-left: 5px; color: #000; font-weight: normal; font-style: normal; text-decoration: none; font-family: Helvetica,Arial,sans-serif; font-size: 12pt;" data-mce-style="border-left: 2px solid #1010FF; margin-left: 5px; padding-left: 5px; color: #000; font-weight: normal; font-style: normal; text-decoration: none; font-family: Helvetica,Arial,sans-serif; font-size: 12pt;"><span style="font-size: large;" data-mce-style="font-size: large;"><span style="font-family: monospace;" data-mce-style="font-family: monospace;"><br><br></span></span><br><div class="moz-cite-prefix">On 9/21/2023 1:00 PM, David Alayachew wrote:<br></div><blockquote cite="mid:CAA9v-_OqfE19u7QUrfMB9rYdUE=-PQvUXUceYy1J2kz_YxVXYw@mail.gmail.com"><div dir="ltr"><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">Hello Amber Dev Team,</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">Let's say I have a record like the following.</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">record ComplexRecord(int blah /* and many more components */) {}</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">Next, let's say that I want to override my toString, to include some derived information.</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">Obviously, putting the derived info into the toString is easy, but how do I go about INCLUDING it with my original implementation of toString that was magically created for me by Java?</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">If I try to fully recreate my toString, I run the risk of it becoming out-of-date upon refactor. Best I can come up with is nesting another record with the toString overloaded. Also not ideal.</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;"><br></div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">Thank you for your time!</div><div class="gmail_default" style="font-family: monospace;" data-mce-style="font-family: monospace;">David Alayachew<br></div></div></blockquote><br><br></blockquote></div></div></body></html>