简体   繁体   中英

Implementing equals, hashCode, and toString on java.lang.reflect.Proxy

I'm writing an application where I need to implement functional interfaces on runtime. I don't know in advance which interfaces to implement, but can resolve the Method object using reflection from the interface's Class object.

For the purpose of simplifying the question, let's say that I'm passing a Function object containing the implementation of the method.

Straight-forward stuff:

@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);
                }

                // Calls to toString, hashCode, and equals go here.
                throw new UnsupportedOperationException(method.getName());
            }
    );
}

So calls to toString , hashCode , and equals currently fail. I clearly don't want that.

From the docs of java.lang.reflect.Proxy :

An invocation of the hashCode , equals , or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object . Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object .

So I can override these methods however I want. That's cool, but I would prefer not to.

Sure, I could make dummy implementations that do more or less the same thing (except for hashCode which is native ) or capture some other dummy object (maybe the invocation handler itself) and invoke the method on that one. But I feel like I should be able to just "fall through" to the methods inherited from java.lang.Object the usual way. Or in other words, call the equivalent of super.hashCode() etc. directly on proxy .

Is there a good/standard way of doing that?

Based on the suggestions in the comments, I ended up defining the following base invocation handler:

public class ObjectInvocationHandler implements InvocationHandler {
    public static final Method HASH_CODE;
    public static final Method EQUALS;
    public static final Method TO_STRING;
    
    static {
        Class<Object> object = Object.class;
        try {
            HASH_CODE = object.getDeclaredMethod("hashCode");
            EQUALS = object.getDeclaredMethod("equals", object);
            TO_STRING = object.getDeclaredMethod("toString");
        } catch (NoSuchMethodException e) {
            // Never happens.
            throw new Error(e);
        }
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        // The clone() method is not handled as there's no way to emulate the behavior of that method.
        // Implementers would have to implement cloning entirely in a subclass.
        if (method.equals(HASH_CODE)) {
            return objectHashCode(proxy);
        }
        if (method.equals(EQUALS)) {
            return objectEquals(proxy, args[0]);
        }
        if (method.equals(TO_STRING)) {
            return objectToString(proxy);
        }
        throw new UnsupportedOperationException(method.getName());
    }
    
    public String objectClassName(Object obj) {
        return obj.getClass().getName();
    }
    
    public int objectHashCode(Object obj) {
        return System.identityHashCode(obj);
    }
    
    public boolean objectEquals(Object obj, Object other) {
        return obj == other;
    }
    
    public String objectToString(Object obj) {
        return objectClassName(obj) + '@' + Integer.toHexString(objectHashCode(obj));
    }
}

Then the proxy is then created using

Proxy.newProxyInstance(
    interfaceType.getClassLoader(),
    new Class<?>[]{interfaceType},
    new ObjectInvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
            if (method.equals(m)) {
                return f.apply(args);
            }
            return super.invoke(proxy, method, args);
        }
    }
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM