Soulman
Soulman

Reputation: 3030

Java: casting Collection.class to Class<Collection<T>>

If I have the following interface:

interface MyElementProcessor<T> {
    void doSomethingWith(T element);
    Class<T> getElementClass();
    /* more functions... */
}

Say that I want to implement this for a generic type like Collection<T>. How do I implement getElementClass()? My best attempt so far is something like this:

class MyCollectionProcessor<T> implements MyElementProcessor<Collection<T>> {
    public void doSomethingWith(Collection<T> element) { /* ... */ }

    public Class<Collection<T>> getElementClass() {
        // Ugly, but the only way I have found to cast to Class<Collection<T>>:
        return (Class<Collection<T>>) (Object) Collection.class;
    }
}

I know that there is no way to specify a Class<Collection<T>> literal in Java, but why isn't it possible to cast Collection.class directly to Class<Collection<T>>? Is there a better way than casting via Object?

Upvotes: 2

Views: 1216

Answers (3)

newacct
newacct

Reputation: 122439

why isn't it possible to cast Collection.class directly to Class<Collection<T>>?

Collection.class has type Class<Collection>. Java will not compile this type cast because it can be proved that it cannot succeed. There is probably no type that is a subtype of both Class<Collection> and Class<Collection<T>>.

In Generics, Foo<A> is never a subtype of Foo<B> if A and B are different and they are types (not wildcards), regardless of the relationship between A and B. Therefore, the only subtypes of Class<Collection> are SubclassOfClass<Collection>. And similarly the only subtypes of Class<Collection<T>> are SubclassOfClass<Collection<T>>. Since these do not intersect, there is no way this cast can theoretically succeed.

Is there a better way than casting via Object?

You could cast via Class<?>; but it is not much better:

return (Class<Collection<T>>) (Class<?>) Collection.class;

Upvotes: 3

Antoine Marques
Antoine Marques

Reputation: 1389

Because of Java generics handling, a Class<List<Integer>> class cannot be obtained (without unchecked cast), but will be Class<List> or Class<List<?>>.
Therefore, if you're working with parameterized type in your ElementProcessor, i suggest that you change your method to be Class<? super T> getElementClass()
Two examples :

interface MyElementProcessor<T> {
    void doSomethingWith(T element);
    Class<? super T> getElementClass();
    /* more functions... */
}

SimpleElementProcessor implements MyElementProcessor<Integer> {
    public void doSomethingWith(Collection<Integer> element) { /* ... */ }

    public Class<Integer> getElementsClass() {
        return Integer.class;
    }
}

CollectionElementProcessor<E> implements MyElementProcessor<Collection<E>> {
    public void doSomethingWith(Collection<Collection<E>> element) { /* ... */ }

    // This works because Class<Collection<?>> is a valid substitute for Class<? super T>
    public Class<Collection<?>> getElementsClass() {
        return (Class<Collection<?>>) Collection.class; // Maybe the cast isn't needed
    }
}



As for obtaining the elements class, you can use reflection : If your type derives MyElementProcessor you can obtain it like this :

for(Type interface :  getClass().getGenericInterfaces()) {
    if(interface instanceof ParameterizedType && ((ParameterizedType) interface).getRawType == MyElementProcessor.class)
        Type myElementsType = ((ParameterizedType) interface).getActualTypeArguments()[0];


This only works for deriving classes, that is, anonymous classes or declared types, it won't work if you use it this way because of type erasure( this example is dummy : it is only an example) :

public <T> MyElementProcessor newInstance() {
    return new MyElementProcessor<T> {
        // overridden methods ...
    };
}

In such a case, you will either :

  • Not obtain a ParameterizedType but rather directly the MyElementProcessor.class, whose type argument is a TypeVariable and does not provide you with it's actual type implementation
  • Obtain a ParameterizedType whose raw type is MyElementProcessor and actual type argument be a TypeVariable

Upvotes: 1

morpheus05
morpheus05

Reputation: 4872

You could pass the class literal to the constructor:

 private final Class<Collection<T>> clazz;

 public MyCollectionProcessor(Class<Collection<T>> clazz) {
      this.clazz = clazz;
 }

  public Class<Collection<T>> getElementClass() {
    return clazz;
  }

No cast needed but an extra parameter...

Upvotes: 1

Related Questions