Kasper van den Berg
Kasper van den Berg

Reputation: 9526

How to invoke generic methods with runtime type information?

My program stores types of parameters mapped to the operation that accepts this type of parameter. When using an explicit type to retrieve a stored operation, invoking the operation's method with an object of the given type as parameter is no problem. However, when using a type that is only implicitly known, invoking the operation's method results in an error:

public class StoredArgumentTypeProblem {
    static class Operation<T> {
        T apply(T arg) {
            return arg;
        }
    }

    static class OperationContainer {
        private Map<Class<?>, Operation<?>> storedOperations = new HashMap<>();
        public <T> void put(Class<T> argType, Operation<T> opp) {
            storedOperations.put(argType, opp);
        }

        public Class<?> getSomeStoredKey() {
            return storedOperations.keySet().iterator().next();
        }

        public <T> Operation<T> get(Class<T> type) {
            // unchecked cast, but should work given restrictions on put.
            return (Operation<T>)storedOperations.get(type);    
        }
    }

    public void test() {
        OperationContainer container = new OperationContainer();
        container.put(Integer.class, new Operation<Integer>());
        container.get(Integer.class).apply(new Integer(1234));

        Class<?> keyType = container.getSomeStoredKey();

        // ERROR: method apply in Operation<T> cannot be applied to given types
        container.get(keyType).apply(keyType.cast(new Integer(5678)));
    }
}

Of course, from Java's point of view the error is completely justified; capture #1 of '?' has nothing to do with capture #2 of '?'. But we humans can see that in this case invoking 'apply(…)' with an argument cast by keyType would work.

Is it possible to 'fool' Java and somehow dynamically apply the stored operation?
Using some type of casting? Using an Annotation? Any other ideas? …

Upvotes: 1

Views: 704

Answers (2)

Paul Bellora
Paul Bellora

Reputation: 55213

This issue is related to limitations of wildcard capture. Wildcards essentially work like independent type parameters, and there's no way to express a relationship between them. As a workaround, you can use a "capture helper" method, which uses an actual type parameter to express that relationship:

private <T> void apply(
        OperationContainer container,
        Class<T> keyType,
        Object argument
) {
    T castArgument = keyType.cast(argument);
    Operation<T> operation = container.get(keyType);
    operation.apply(castArgument);
}

public void test() {
    OperationContainer container = new OperationContainer();
    container.put(Integer.class, new Operation<Integer>());
    container.get(Integer.class).apply(new Integer(1234));

    Class<?> keyType = container.getSomeStoredKey();

    apply(container, keyType, new Integer(5678));
}

Upvotes: 2

Kasper van den Berg
Kasper van den Berg

Reputation: 9526

When writing the question above, the following solution occured to me.

To solve problems caused by generics and reflection …
use more reflection!

Given Operation and OperationContainer defined as above use Class.getMethod(…) and Method.invoke(…):

    public void test() {
        OperationContainer container = new OperationContainer();
        container.put(Integer.class, new Operation<Integer>());
        container.get(Integer.class).apply(new Integer(1234));
        Class<?> keyType = container.getSomeStoredKey();

        // ERROR: method apply in Operation<T> cannot be applied to given types
        // container.get(keyType).apply(keyType.cast(new Integer(5678)));

        Operation<?> storedOpp = container.get(keyType);
        try {
            storedOpp.getClass().getMethod("apply", keyType).invoke(storedOpp, keyType.cast(new Integer(5678)));
        } catch (IllegalAccessException | IllegalArgumentException |
                InvocationTargetException | NoSuchMethodException ex) {
            throw new Error(ex);
        }
    }

Upvotes: 0

Related Questions