7

I need to pass the class of a generic type into a class's constructor. The class is SpiceRequest from the RoboSpice Android library for a reference to the constructor.

It seems odd that the class requires passing the generic's class into the contstructor, when it could be accessed from the generic type itself, in this case RESULT.class, but maybe I'm wrong about this. Anyway, I'm not looking to change the library's code, but rather need to use a generic type for the generic type of SpiceRequest, Map<String, ? extends Object>. Here's my code:

SpiceRequest<Map<String, ? extends Object>> request =
        new SpiceRequest<Map<String, ? extends Object>>(???) {
            ...
        };

And the signature of the SpiceRequest constructor:

public SpiceRequest(final Class<RESULT> clazz) {
    ...
}

For ??? I have tried Map.class with the compiler error: The constructor SpiceRequest<Map<String,? extends Object>>(Class<Map>) is undefined.

Map<String, ? extends Object>.class gives the error: Syntax error on tokens, PrimitiveType expected instead, specifically underlining ? extends Object. It also gives the same error as Map.class.

And Map.<String, ? extends Object>class gives the same compiler error as well.

What is the correct way to get the generic class Class<Map<String, ? extends Object>>?

Snicolas
  • 37,840
  • 15
  • 114
  • 173
Jeff Lockhart
  • 5,379
  • 4
  • 36
  • 51
  • 1
    http://stackoverflow.com/questions/7502243/java-casting-class-operator-used-on-a-generic-type-e-g-list-to-classlist – fscan Jan 19 '13 at 22:38
  • 1
    http://stackoverflow.com/questions/2012306/how-to-create-expressions-of-type-classlist has information about this too. It seems there is no elegant way to do this in Java other than explicit up casting and then down casting. – Jeff Lockhart Jan 19 '13 at 22:53
  • Sorry about previous answer, was unclear what you were trying to do. – sage88 Jan 19 '13 at 22:53

4 Answers4

5

There are no class literals for concrete parameterized types or wildcard parameterized types. From Angelika Langer's generics tutorial:

Wildcard parameterized types lose their type arguments when they are translated to byte code in a process called type erasure. As a side effect of type erasure, all instantiations of a generic type share the same runtime representation, namely that of the corresponding raw type. In other words, parameterized types do not have type representation of their own. Consequently, there is no point to forming class literals such as List<?>.class , List<? extends Number>.class and List<Long>.class, since no such Class objects exist. Only the raw type List has a Class object that represents its runtime type. It is referred to as List.class.

There are no class literals for concrete parameterized types for the same reasons, which in a nutshell are type erasure.

For your purposes, you will just need to do an unchecked cast of the class literal:

Class<Map<String, ?>> c = (Class<Map<String, ?>>)(Class<?>)Map.class;

Note that the double cast through Class<?> is necessary because a direct conversion from Class<Map> to Class<Map<String, ?>> is illegal.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • 1
    @gavenkoa Since generics [aren't covariant](http://stackoverflow.com/q/2745265/697449), it's a compile error to cast directly from `Class` to `Class>`. – Paul Bellora Nov 27 '13 at 14:50
4

this problem is not really related to RoboSpice itself but to a limitation of the Java syntax : there is no litteral that you can use to get the class/type of a parametrized generic type in Java.

If you want to do something like

public class ListTweetRequest extends SpiceRequest<List<Tweet>> {

   public ListTweetRequest(Object cacheKey ) {
       super( <Here is the problem>, cacheKey );
   }
}

then you can't pass the class List<Tweet>.class to you parent constructor. That's really a Java limitation as generics are realized using type erasure and List<Tweet>.class has no real meaning in Java.

The best and cleanest work around is to use an intermediate type like :

public class ListTweet extends List<Tweet> {
}

public class ListTweetRequest extends SpiceRequest<ListTweet> {

   public ListTweetRequest(Object cacheKey ) {
       super( ListTweet.class, cacheKey );
   }
}

This has the advantage to provide you with a real type that you can pass to the SpiceRequest's constructor. But beware of obfuscation. In thoses cases, proguard will try to remove the ListTweet class, you have to explicitly preserve it from obfuscation.

Snicolas
  • 37,840
  • 15
  • 114
  • 173
0

I am always guessing with erasure, but have you tried just calling it with Map.class?

Luis
  • 1,294
  • 7
  • 9
  • Yes, actually that was the first thing I tried. I'll update the question with that info. – Jeff Lockhart Jan 19 '13 at 22:00
  • Are you getting an error? Could you also add the error you get (I think it is a runtime error, right? ) – Luis Jan 19 '13 at 22:02
  • No, it's a compiler error: `The constructor SpiceRequest>(Class) is undefined`. – Jeff Lockhart Jan 19 '13 at 22:03
  • My only other guess is replacing the ? with T, or maybe just using Map and trying again the same combinations you already did. – Luis Jan 19 '13 at 22:09
  • Thinking about it, ? extends Object is no final type, so you cannot get a class from it – fscan Jan 19 '13 at 22:20
  • I tried `Map` and get `T cannot be resolved to a variable`, `Map cannot be resolved to a variable`, `String cannot be resolved to a variable`. So I feel like this syntax is just completely not allowed. All of the other things I have tried have just been getting `Map.class` back. – Jeff Lockhart Jan 19 '13 at 22:22
  • @fscan Please elaborate. Are all generic classes not a final type? Because `Map.class` doesn't work if I try to define the class that way too (same `cannot be resolved to a variable` compiler errors as my previous comment). – Jeff Lockhart Jan 19 '13 at 22:25
  • @jeffdgr8 like `new SpiceRequest>(Map.class)` ? – fscan Jan 19 '13 at 22:27
  • Yes, exactly. Same as my code above, but replaced all `Map` with `Map`. – Jeff Lockhart Jan 19 '13 at 22:30
  • Hmm, have you tried with HashMap? Maybe the problem is the interface? At least you could say `new HashMap<...>().getClass()` :) – fscan Jan 19 '13 at 22:32
0

I got that problem as well and I found solution like @Snicolas was saying.

Look at this example of robospice-sample-retrofit at :

https://github.com/octo-online/RoboSpice-samples/blob/release/robospice-sample-retrofit/src/com/octo/android/robospice/sample/retrofit/network/SampleRetrofitSpiceRequest.java

Actually is better to inherit not from raw List interface but any subclass like ArrayList etc., otherwise you will have to implement all interface methods.

import java.util.ArrayList;

public class Contributor {
    public String login;
    public int contributions;

    @SuppressWarnings("serial")
    public static class List extends ArrayList<Contributor> {
    }
}