Home > OS >  Java Generics: getting return type from parameter
Java Generics: getting return type from parameter

Time:09-07

This is an odd question. I don't think there's a solution, but I thought I'd ask anyway.

Say I have an enum:

public enum Key {
    RED(String.class),
    GREEN(Integer.class),
    BLUE(Short.class);

    private Class<?> expectedType;

    Key(Class<?> expectedType) { this.expectedType = expectedType; }
    public Class<?> getExpectedType() { return expectedType; }
}

I want to use the 'expectedType' field from the Key enum as the return type of a method. See:

public class Cache {

    private static Map<Key, Object> cache = new HashMap<>();

    public void put(Key key, Object value) {
        // Easy to validate that 'value' is of type key.getExpectedType()...
    } 

    public <T> T get(Key key) {
        Object value = cache.get(key);
        // TODO need to define <T> as key.getExpectedType(). How?
        
    }
}

See that TODO? I'd like for get() to define the return type of the 'expectedType' defined by the key parameter. E.g. if the key parameter were RED, the get() method would return a String and you could write:

    String s = cache.get(Key.RED);

Is there a way to do that? I'm thinking there isn't, but I'd love to hear of a clever solution.

CodePudding user response:

Enums don't support generics, but you could use a regular class as a generic pseudo-enum:

public class Key<T> {
    public static final Key<String> RED = new Key<>(String.class);
    public static final Key<Integer> GREEN = new Key<>(Integer.class);
    public static final Key<Short> BLUE = new Key<>(Short.class);

    private final Class<T> expectedType;

    private Key(Class<T> expectedType) { this.expectedType = expectedType; }
    public Class<T> getExpectedType() { return expectedType; }
}

public class Cache {

    private Map<Key<?>, Object> cache = new HashMap<>();

    public <T> void put(Key<T> key, T value) {
        cache.put(key, key.getExpectedType().cast(value));
    } 

    public <T> T get(Key<T> key) {
        return key.getExpectedType().cast(cache.get(key));
    }
}

CodePudding user response:

shmosel's answer is almost certainly sufficient for what you need; however, it has the slight limitation that you can't store/retrieve a generic type, because you can't get a class literal for a generic type.

Instead, you can use something like Guava's TypeCapture:

abstract class GenericKey<T> {
  Type getExpectedType() {
    return ((ParameterizedType) getClass().getGenericSuperclass())
        .getActualTypeArguments()[0];
  }
}

which is a bit of reflective grossness that you shouldn't spend too much time looking at.

Notice that it's abstract, so you have to instantiate like:

new GenericKey<Integer>() {}

This is creating an anonymous subclass of GenericKey, which is part of the magic that makes it work with generic types.

Then, it's basically the same:

public class Cache {

    private Map<GenericKey<?>, Object> cache = new HashMap<>();

    public <T> void put(GenericKey<T> key, T value) {
        cache.put(key.getExpectedType(), value);
    } 

    public <T> T get(GenericKey<T> key) {
        return (T) cache.get(key.getExpectedType());
    }
}

Now you could have a GenericKey<List<Integer>>, using new new GenericKey<List<Integer>() {}, if you should so desire.

The downside of this approach is that you lose the ability to do checking on the value on the way in/out of the cache, so you could get heap pollution if you are careless with raw types.

  • Related