Return a Class instance with its generic type
Asked Answered
F

2

12

Here's a simple example that demonstrates a type-erasure-related issue I am running into. I have a class like this:

public abstract class AbstractHandler<T> {

    ...
    public abstract Class<T> handledType();
}

Then I have this implementation:

public class ConcreteHandler extends AbstractHandler<Map<String, List<Thing>>> {

    @Override
    public Class<Map<String, List<Thing>>> handledType() {
        //what do I return here?!
    }
}

I can't return Map<String, List<Thing>>.class, since that's not even valid syntactically. I tried making the generic type-parameter in the subtype to be HashMap<String, List<Thing>> and then returning new HashMap<String, List<Thing>>().getClass(), but that doesn't work because the return type of Class<T>#getClass() is Class<? extends T>. I looked at TypeToken from Guava, and the getRawType method seemed promising, but it returns Class<? super T>.

I have a workaround for the time being that looks like this:

public class ThingListMap {
    private Map<String, List<Thing>> thingListMap;

    ...
}

and I just use ThingListMap as the generic type-parameter.

Another possible workaround is to perform a forced cast:

public Class<Map<String, List<Thing>>> handledType() {
    return (Class<Map<String, List<Thing>>>) new HashMap<String, List<Thing>>().getClass();
}

Is there a more-elegant way to do this?

EDIT: In response to one of the answers, I cannot change the signature of the handledType method since I do not own or control its source.

Fulllength answered 15/4, 2015 at 20:58 Comment(8)
Need more info on what's the purpose of that methodManchester
Is there any way to do this?" In plain Java, no.Adjacent
@Manchester This is more of a conceptual question. I do have a workaround, but I wanted to see if there was a more elegant way.Fulllength
@MaxZoom No, that won't work; the return type is Class<T>.Fulllength
I had kind-of a thing lkike that and we added a factory mwthod for creating inatances of the templated but never to return the class.Manchester
The thing you'll be returning will be Map.class, regardless of what actual type you cast that too; there's no way around that. (Except if you do the new HashMap<Foo, Bar>().getClass(), in which case it will be HashMap.class, cast to some other type, but the actual object will still be a raw class type.)Edris
Your class is not well conceived. Instances of class java.lang.Class represent classes (duh), but you are trying to use one to represent a type, which is something more general. Java does have a Type class for this concept, but it probably won't serve your purposes either.Band
@JohnBollinger I completely agree, but I didn't design this class and so I cannot change it. :(Fulllength
S
8

For some reason, Java doesn't allow you to cast Map.class directly to Class<Map<String, List<Thing>>>. It's an unchecked cast anyway.

But it's legal to cast it twice, first to Class<?>, then to Class<Map<String, List<Thing>>>.

return (Class<Map<String, List<Thing>>>) (Class<?>) Map.class;

Being an unchecked cast, you may want to add @SuppressWarnings("unchecked").

Spousal answered 15/4, 2015 at 21:7 Comment(5)
@Manchester I don't understand your comment or how it applies to this answer.Spousal
"For some reason [...]" Basically the same reason you can't cast a List<Cat> to a List<Dog>. Neither is a supertype or subtype of the other. See also "Cannot convert from List<List> to List<List<?>>". RE: this answer here, casting is fine for something like object creation but note that it's the case that e.g. Class<Map<String, String>> will == a Class<Map<Integer, Integer>>.Adjacent
This is a better (subjective, I guess :p) variation of my forcible cast of an ad-hoc HashMap<K, V> instance's class. I like the fact that I don't have to create that ad-hoc instance.Fulllength
@Adjacent Yes, that's the reason. Thanks for pointing that out.Spousal
@VivinPaliath Also, this is closer to the real type, as you are returning HashMap.class rather than Map.class.Binah
C
4

Guava's approach to this is to use TypeTokens. Your class would become

public abstract class AbstractHandler<T> {
    public TypeToken<T> handledType();
}

public class ConcreteHandler extends AbstractHandler<Map<String, List<Thing>>> {
    @Override
    public TypeToken<Map<String, List<Thing>>> handledType() {
        return new TypeToken<Map<String, List<Thing>>>() {};
    }
}
Chromophore answered 15/4, 2015 at 21:9 Comment(1)
Yes, that would be ideal. Unfortunately I cannot change the signature of handledType in AbstractHandler<T>. I will mention that in the question.Fulllength

© 2022 - 2024 — McMap. All rights reserved.