Question about records and overriding their "magic methods"

Remi Forax forax at univ-mlv.fr
Thu Sep 21 17:49:14 UTC 2023


> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "David Alayachew" <davidalayachew at gmail.com>, "amber-dev"
> <amber-dev at openjdk.org>
> Sent: Thursday, September 21, 2023 7:18:13 PM
> Subject: Re: Question about records and overriding their "magic methods"

> We explored this topic somewhat when designing the feature.

> 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 ==.)

> 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.
If you really really want to do it, you can use the bootstrap method in java.lang.runtime.ObjectsMethods but it's ugly. 

public class CallingRecordToStringDemo { 

record Foo ( String blah, int value) { 
private static final MethodHandle TO_STRING ; 
static { 
var lookup = MethodHandles . lookup (); 
var components = Foo . class .getRecordComponents(); 
CallSite callsite ; 
try { 
callsite = ( CallSite ) ObjectMethods . bootstrap ( lookup , 
"toString" , 
MethodType . methodType ( String . class , Foo . class ), 
Foo . class , 
Arrays . stream ( components ).map( RecordComponent ::getName).collect( Collectors . joining ( ";" )), 
Arrays . stream ( components ).map(c -> { 
try { 
return lookup .unreflect(c.getAccessor()); 
} catch ( IllegalAccessException e) { 
throw new AssertionError(e); 
} 
}).toArray( MethodHandle []:: new )); 
} catch ( Throwable e) { 
throw new AssertionError(e); 
} 
TO_STRING = callsite .dynamicInvoker(); 
} 

@Override 
public String toString () { 
try { 
return ( String ) TO_STRING .invokeExact( this ); 
} catch ( Error e) { 
throw e; 
} catch ( Throwable e) { 
throw new AssertionError(e); 
} 
} 
} 

public static void main ( String [] args) { 
System . out .println( new Foo( "blah" , 42 )); 
} 
} 

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. 
The record methods toString/equals/hashCode will be declare as abstract bridge method in that case. 

regards, 
Rémi 

> On 9/21/2023 1:00 PM, David Alayachew wrote:

>> Hello Amber Dev Team,

>> Let's say I have a record like the following.

>> record ComplexRecord(int blah /* and many more components */) {}

>> Next, let's say that I want to override my toString, to include some derived
>> information.

>> 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?

>> 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.

>> Thank you for your time!
>> David Alayachew
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230921/2de4ef95/attachment-0001.htm>


More information about the amber-dev mailing list