Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Generic types in a type token

I have a class which takes a type token, and then generates objects of a type parameterized by that type (OK, it's a bit more complicated than that, but this is a concise example):

public class Test {

    public static void main(String[] args) throws Exception {
        Holder<HashSet<Integer>> i = newObjectHolder(HashSet.class);  // this fails
    }

    static class Holder<T> {
        public Holder(T newInstance) {}
    }

    public static <T> Holder<T> newObjectHolder(Class<T> typeToken) throws Exception {
        Constructor<T> ctor = typeToken.getConstructor();
        return new Holder<T>(ctor.newInstance());
    }
}

This works fine if passed non-generic types, like:

Holder<Integer> i = Test.newObjectHolder(Integer.class);

If the passed type token is generic, however, it doesn't work, as in the indicated line above:

Holder<HashSet<Integer>> i = Test.newObjectHolder(HashSet.class);  // this fails

I get the problem, but is there a solution? I'm OK to add @SuppressWarnings("unused") in the code for newObject, if it doesn't reduce the safety. Intuitively, it seems that newObject should be able to to make a cast that works, we know that one "new" object of an erased generic type is the same as any other, and we haven't otherwise used T in the method.

like image 531
BeeOnRope Avatar asked Mar 02 '12 05:03

BeeOnRope


2 Answers

So, sheepishly, answering my own question...

I can't find any way to do it with Class<?>, but this magic technique called super type tokens seems to work well. Here's the updated code:

public class Test {

    static class A {}
    static class B extends A {}


    public static void main(String[] args) throws Exception {
        Holder<HashSet<Integer>> i = newObjectHolder(new TypeReference<HashSet<Integer>>() {});  // works
        Holder<A> j = newObjectHolder(new TypeReference<A>() {});  // works
        Holder<A> k = newObjectHolder(new TypeReference<B>() {});  // works
        Holder<B> l = newObjectHolder(new TypeReference<A>() {});  // doesn't compile (good)
    }

    static class Holder<T> {
        public Holder(T newInstance) {}
        T get() { return null; }
    }

    public static <T,U extends TypeReference<? extends T>> Holder<T> newObjectHolder(U typeToken) throws Exception {
        T obj = typeToken.newInstance();
        return new Holder<T>(obj);
    }
}

The TypeReference code is given here, although I suspect that Guice's TypeLiteral would work just as well (but it doesn't have newInstance code, you'd have to implement it).

like image 139
BeeOnRope Avatar answered Oct 14 '22 01:10

BeeOnRope


In your, now updated, code, you still can

 Holder<HashSet> i = newObjectHolder(HashSet.class);

but can't

 Holder<HashSet<Integer>> i = newObjectHolder(HashSet.class);

Old answer:

I am not sure what problem you're getting. This code works alright.

public class Test {

    public static void main(String[] args) throws Exception {
        @SuppressWarnings("unchecked")
        HashSet<Integer> i = newObject(HashSet.class);
        i.add(new Integer(42));
        i.add(new Integer(666));
        System.out.println(i.size() +":" + Arrays.toString(i.toArray()));
    }

    public static <T> T newObject(Class<T> typeToken) throws Exception {
        Constructor<T> ctor = typeToken.getConstructor();
        return ctor.newInstance();
    }

}

prints

2:[666, 42]

Am I missing anything?

like image 40
Nishant Avatar answered Oct 14 '22 00:10

Nishant