4

I have a class that uses generics. Because it needs a reference to the generic type, the constructor has a Class parameter (as discussed in this question), so it looks like this:

MyGenericClass<T>
public MyGenericClass( final Class<T> valueClass ) {
    this.clazz = valueClass;
}

Now I want to pass MyGenericClass as the T, but I can't get it working.

MyGenericClass<MyGenericClass<String>> foo = 
        new MyGenericClass<MyGenericClass<String>>(MyGenericClass<String>.class);

is giving me a compiler error.

It looks like it's a problem of Type Erasure (see this SO question). With this dual layer of generics, I'm not sure if it's possible to do what I want to do.

Any suggestions?

UTA:

I can't post the entire code because it's corporate code and I'm not sure of the rules of posting. The class is essentially a MultipleValueMap that maps a single key to a list of values. The class object is used to create a typed array of the values in the list (using list.toArray(T[]))

In this case, I want to have a key that maps to more than one list of values. And that's causing the issue.

Community
  • 1
  • 1
chama
  • 5,973
  • 14
  • 61
  • 77
  • Is this the real code copied, or a typing error? A ">" is missing after String and before ")". – Roger Gustavsson May 12 '15 at 13:01
  • It depends on what you do with `this.clazz`. What do you use it for? – Radiodef May 12 '15 at 13:07
  • Do you really use this `clazz` object? – Dmitry Ginzburg May 12 '15 at 13:07
  • @RogerGustavsson Fixed that. It was a typing error. Whoops – chama May 12 '15 at 13:13
  • @Radiodef I'm using it to create an array of Ts. I tried passing the array into the constructor instead of the Class object, but I'm having the same kind of issues. – chama May 12 '15 at 13:13
  • I tried figuring this out on my own, but apparently what you get back in that case isn't a `Class`, and GSON (the JSON parser) uses the following class `$Gson$Types` http://grepcode.com/file/repo1.maven.org/maven2/com.google.code.gson/gson/2.3.1/com/google/gson/internal$/Gson$Types.java#%24Gson%24Types (or should I say http://grepcode.com/file_/repo1.maven.org/maven2/com.google.code.gson/gson/2.3.1/com/google/gson/internal$/Gson$Types.java/?v=source ) where it replaces all `com.sun.internal...ParametrizedTypeImpl` with their own implementations, then create the list based on that. – EpicPandaForce May 12 '15 at 13:39
  • *"In this case, I want to have a key that maps to more than one list of values. And that's causing the issue."* So are you saying that `MyGenericClass` is a key in the map? (And maybe you need a `MyGenericClass.class` and a `MyGenericClass.class` so the keys hash different?) In any case, from reading some of your comments, there just isn't a clear solution to it. : ( – Radiodef May 12 '15 at 13:54

5 Answers5

3

Generics begin to break down in a situation such as this. List<String> is a type but not a class, so there's only a List.class.

Guava's TypeToken was created for these situations.

class MyGenericClass<T> {
    private Class<T> raw;

    /* the code in this constructor
     * has similar semantics to e.g.
     *  MyGenericClass<String>[] arr = new MyGenericClass[10];
     * when passing a
     *  new TypeToken<MyGenericClass<String>>() {}
     */
    MyGenericClass(TypeToken<T> type) {
        @SuppressWarnings("unchecked")
        final Class<T> unchecked = (Class<T>) type.getRawType();
        raw = unchecked;

        @SuppressWarnings("unchecked")
        T[] arr = (T[]) Array.newInstance(type, 10);
        //
    }
}
MyGenericClass<MyGenericClass<String>> g =
    new MyGenericClass<>(new TypeToken<MyGenericClass<String>>() {});

Otherwise your options are a bit limited. There are kludgy options:

MyGenericClass<MyGenericClass<String>> g =
    new MyGenericClass<MyGenericClass<String>>(
        (Class) MyGenericClass.class);

Also possibly changing Class<T> to Class<? extends T> and passing a subclass (which I see somebody's already posted so I won't bother to repeat it).

If you don't need to return the array to the outside world, you could do:

class MyGenericClass<T> {
    private Class<? super T> maybeSuper;

    MyGenericClass(Class<? super T> maybeSuper) {
        this.maybeSuper = maybeSuper;

        // array which accepts T
        Object[] arr = (Object[]) Array.newInstance(maybeSuper, 10);
    }
}

Then you can do:

MyGenericClass<MyGenericClass<String>> g =
    new MyGenericClass<MyGenericClass<String>>(
        MyGenericClass.class);

because the raw type is a supertype of a parameterized type.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • I can't introduce an extra dependency to Guava, but thanks for your answer. – chama May 12 '15 at 13:36
  • Well you can still use something like it without Guava. See http://gafter.blogspot.com/2006/12/super-type-tokens.html – Radiodef May 12 '15 at 13:37
0

I don't think you want to use Class. This expects a Class object.

Just use T or Object<T> instead if Class. This is what it seems like you want from your example

ControlAltDel
  • 33,923
  • 10
  • 53
  • 80
  • I need the Class object so I can create a T[]. Can't do that with just the T or Object – chama May 12 '15 at 13:14
  • You can't use a `List`? You understand that Generics only exist at compile time, right? That the only purpose they serve is to enhance compile time type checking? – ControlAltDel May 12 '15 at 13:26
  • I need the array to be able to do a List.toArray(T[]). – chama May 12 '15 at 13:37
  • @chama arrays are really not that good to use with generic classes. Can you eliminate them in your application in favor of `List`? – Dmitry Ginzburg May 12 '15 at 13:38
0

From what I've seen, it seems to me, that you want to instantiate Class<MyGenericClass<String>> in some way like MyGenericClass<String>.class. This way is prohibited and you have to use another option: you may accept Class<? extends T> as a parameter for constructor and create something like inner class, which extends generic class with concrete generification:

private static class MGCString extends MyGenericClass<String> {
}

and instantiate your new member as

MyGenericClass<MyGenericClass<String>> foo = new MyGenericClass<>(MGCString.class);

Note from @Radiodef: This depends on what you are doing with the array you create. If you want to put e.g. a new GenericClass<String>(String.class) into the array, then this doesn't work. (It throws array store exception.) If you are instantiating the elements with reflection too (through class.newInstance(), for example) then possibly it works.

AFTER UPDATE

Since you've posted the update for the question, I can now answer your question :) The usual way to convert List to array is to move List's elements to some array, which is provided by user.

For example, we can look at List.toArray(T[]). This method puts all of the list's elements into passed array in case it is big enough. Else it creates new array (yes, we can get its type by running a.getClass()).

Also, in Java 8, Streams API introduced the brand new way to do the same thing: to provide the generator for T[]:

//get new Stream<String>, for example, someway
        .toArray(String[]::new)

You can add this ability to your code, if you create some method, which accepts IntFunction<T[]> arrayGenerator and runs arrayGenerator.apply(list.size()) to get the array of requested size.

Dmitry Ginzburg
  • 7,391
  • 2
  • 37
  • 48
  • This depends on what the OP is doing with the array they create. If they want to put e.g. a `new GenericClass(String.class)` in to the array, then this doesn't work. (It throws array store exception.) If they are instantiating the elements with reflection too then possibly it works. – Radiodef May 12 '15 at 13:32
  • @Radiodef seems legit. Maybe I should include your comment into the answer. – Dmitry Ginzburg May 12 '15 at 13:36
  • I use the array for list.toArray(array), so I'm not sure if this could work for me. – chama May 12 '15 at 13:40
  • @chama Can you post the full example for us to know what we're trying to achieve? – Dmitry Ginzburg May 12 '15 at 13:42
  • I posted more detail. Not sure if I'm allowed to post the full code. – chama May 12 '15 at 13:51
  • @chama of course, you should not post the entire code; you can and should post only the code, which shows your intention (enough code). – Dmitry Ginzburg May 12 '15 at 13:58
  • We are still using Java 6 :( I wonder if I can forget the array producing capability and just use a List. Thanks for your help! – chama May 12 '15 at 14:12
  • @chama I wrote the old method before Java 8 :-) – Dmitry Ginzburg May 12 '15 at 15:00
0

You can do it, sort of..,

Your basic problem is that the type T of Class<T> can not itself be generic, that is:

Class<List> a; // OK
Class<List<String>> b; // can't do this

But! You can get around this by creating a type to prepresent the typed type, eg:

interface StringList extends List<String> {}
Class<? extends StringList> c; OK!

Translating this to your situation, you would have to create an interface to prepresent MyGenericClass (to avoid the constructor problem in the abstract type) then have subtypes of that and pass those in to your constructors.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
0

It kind of depends on what your purpose is.

You can get an expression of type Class<MyGenericClass<String>> by doing some unsafe casting of MyGenericClass.class:

(Class<MyGenericClass<String>>)(Class<?>)MyGenericClass.class

But is this really what you want? Why need the unsafe casts?

In some sense, you can't "truly" have a Class<MyGenericClass<String>>, because Class<T> can check at runtime whether a given object is type T, with its .isIntance() method, but you can never check an object's generic type arguments at runtime because it's erased. For some purposes however, it may be good enough to "pretend" that MyGenericClass.class (of type Class<MyGenericClass>) is a Class<MyGenericClass<String>> (which is what the unsafe casts above are doing).

So it comes down to what your function does with the class object. From your description, you say "The class object is used to create a typed array of the values in the list (using list.toArray(T[]))". As you know, generic array creation is not allowed in Java. So even if you were working with the concrete class directly instead of T, you still couldn't do new MyGenericClass<String>[42]. The best you can do is (MyGenericClass<String>[])new MyGenericClass[42] or (MyGenericClass<String>[])new MyGenericClass<?>[42]. In other words, you are creating the array using the raw-type class, and then "pretending" that the created array is an array of the parameterized type using unsafe casts.

This is analogous to the casts above with the class object:

(Class<MyGenericClass<String>>)(Class<?>)MyGenericClass.class

-- you are using the raw-type class object, and then "pretending" that the class is of the parameterized type using unsafe casts, so that when you make the array you will think it's an array of the parameterized type.

newacct
  • 119,665
  • 29
  • 163
  • 224