Skip to content

Instantly share code, notes, and snippets.

@nealeu
Created March 25, 2026 19:44
Show Gist options
  • Select an option

  • Save nealeu/73d66e635dda4f70543409148eaecd78 to your computer and use it in GitHub Desktop.

Select an option

Save nealeu/73d66e635dda4f70543409148eaecd78 to your computer and use it in GitHub Desktop.
Re-using record ObjectMethod approach for hashCode, equals and toString
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