如何让Java动态代理的equals/hashCode/toString沿用Object默认实现?
Great question! This is a common gotcha with Java's dynamic proxies—since toString(), hashCode(), and equals(Object) are routed through your InvocationHandler instead of using the default Object implementations, your current code throws exceptions for these calls when you don't want it to.
Below are two standard approaches to fix this and get the default Object behavior you're looking for:
Solution 1: Mimic Object's Default Behavior (Java 8+)
The easiest way to replicate the exact default Object logic without fancy reflection is to handle these three methods explicitly in your handler. This approach is straightforward and works across all modern Java versions:
@SuppressWarnings("unchecked") private <T> T implement(Class<T> interfaceType, Method m, Function<Object[], ?> f) { return (T) Proxy.newProxyInstance( getClass().getClassLoader(), new Class<?>[]{interfaceType}, (proxy, method, args) -> { if (method.equals(m)) { return f.apply(args); } // Handle Object's core methods with their default logic if (method.getDeclaringClass() == Object.class) { return switch (method.getName()) { case "toString" -> proxy.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(proxy)); case "hashCode" -> System.identityHashCode(proxy); case "equals" -> proxy == args[0]; default -> throw new UnsupportedOperationException(method.getName()); }; } throw new UnsupportedOperationException(method.getName()); } ); }
This matches Object's default behavior perfectly:
System.identityHashCode(proxy)returns the same identity hash code thatObject's defaulthashCode()uses (tied to the object's memory address).proxy == args[0]is exactly whatObject'sequals(Object)does—it only returnstrueif the argument is the exact same object reference.- The
toString()output follows the standardClassName@HexIdentityHashCodeformat you get from a plainObject.
Solution 2: Directly Invoke Object's Methods with MethodHandle (Java 7+)
If you prefer to call the actual Object method implementations instead of mimicking them, you can use MethodHandles to bypass the proxy's interception logic. This is a more "pure" approach but requires a bit more reflection code:
First, add a helper method to fetch and invoke the original Object method:
private Object invokeObjectDefaultMethod(Object proxy, Method method) throws Throwable { // Look up the original method from Object, skipping the proxy's override MethodHandle methodHandle = MethodHandles.lookup() .in(proxy.getClass()) .findSpecial(Object.class, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), proxy.getClass()); return methodHandle.invoke(proxy); }
Then update your proxy handler to use this helper:
@SuppressWarnings("unchecked") private <T> T implement(Class<T> interfaceType, Method m, Function<Object[], ?> f) { return (T) Proxy.newProxyInstance( getClass().getClassLoader(), new Class<?>[]{interfaceType}, (proxy, method, args) -> { if (method.equals(m)) { return f.apply(args); } // Delegate to Object's actual default methods if (method.getDeclaringClass() == Object.class) { return invokeObjectDefaultMethod(proxy, method); } throw new UnsupportedOperationException(method.getName()); } ); }
What's happening here:
findSpecialretrieves the method definition fromObject(the parent class of all proxies) instead of using the proxy's overridden version. This skips the proxy's interception and calls the realObjectmethod directly.- Note: In restricted security environments (like some app servers), you might need to adjust permissions for this to work, but it's fully compliant with Java's standard library.
Either approach will give you the default Object behavior you're looking for. The first solution is simpler and more portable, while the second is ideal if you want to directly invoke the original Object methods rather than replicating their logic.
内容的提问来源于stack exchange,提问作者bisgardo

