Created
March 25, 2026 19:44
-
-
Save nealeu/73d66e635dda4f70543409148eaecd78 to your computer and use it in GitHub Desktop.
Re-using record ObjectMethod approach for hashCode, equals and toString
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.lang.invoke.ConstantCallSite; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.MethodType; | |
| import java.lang.runtime.ObjectMethods; | |
| /** | |
| * This demonstrates how the internals that provide methods for Java records can also be used on | |
| * other classes. This approach could be benchmarked against Lombok's implemetation to see | |
| * the effect on compilation speed (it may be slightly simpler code generation), class size | |
| * and runtime performance. | |
| */ | |
| public class ObjectMethodsBootstrapTest { | |
| public static void main(String[] args) { | |
| var obj1 = new ObjectMethodsBootstrapTest("asdf", 1); | |
| var obj1match = new ObjectMethodsBootstrapTest("asdf", 1); | |
| var obj2 = new ObjectMethodsBootstrapTest("asdf", 2); | |
| System.out.println(obj1.hashCode()); | |
| System.out.println(obj2.hashCode()); | |
| System.out.println(obj1.equals(obj1match)); | |
| System.out.println(obj1.equals(obj2)); | |
| System.out.println(obj1); | |
| System.out.println(obj2); | |
| } | |
| private final String x; | |
| private final int y; | |
| private static ConstantCallSite hashHandle; | |
| ObjectMethodsBootstrapTest(String x, int y) { | |
| this.x = x; | |
| this.y = y; | |
| } | |
| @Override | |
| public int hashCode() { | |
| if (hashHandle == null) { | |
| hashHandle = getHandle("hashCode", int.class); | |
| } | |
| try { | |
| return (int) hashHandle.getTarget().bindTo(this).invokeExact(); | |
| } catch (Throwable e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| private static ConstantCallSite equalsHandle; | |
| @Override | |
| public boolean equals(Object o) { | |
| if (equalsHandle == null) { | |
| equalsHandle = getHandle("equals", boolean.class, Object.class); | |
| } | |
| try { | |
| return (boolean) equalsHandle.getTarget().bindTo(this).invokeExact(o); | |
| } catch (Throwable e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| private static ConstantCallSite toStringHandle; | |
| @Override | |
| public String toString() { | |
| if (toStringHandle == null) { | |
| toStringHandle = getHandle("toString", String.class); | |
| } | |
| try { | |
| return (String) toStringHandle.getTarget().bindTo(this).invokeExact(); | |
| } catch (Throwable e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| private static ConstantCallSite getHandle(String methodName, Class<?> returnType, Class<?>... parameterTypes) { | |
| try { | |
| var existing = MethodHandles.lookup().findVirtual(ObjectMethodsBootstrapTest.class, methodName, MethodType.methodType( | |
| returnType, parameterTypes)); | |
| var xHandle = MethodHandles.lookup().findGetter(ObjectMethodsBootstrapTest.class, "x", String.class); | |
| var yHandle = MethodHandles.lookup().findGetter(ObjectMethodsBootstrapTest.class, "y", int.class); | |
| return (ConstantCallSite) ObjectMethods.bootstrap(MethodHandles.lookup(), methodName, existing.type(), ObjectMethodsBootstrapTest.class, "x;y", xHandle, yHandle ); | |
| } catch (Throwable e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment