Ed Marty
Ed Marty

Reputation: 39700

Filtering a collection to a specified type generates a compiler warning

I'm attempting to write a method that will take a Collection<?> and remove all objects that are not of type T, returning either the original collection or a new collection, shown in this method signature:

public static <T,C extends Collection<T>> C objectsOfType(Collection<?> in, Class<T> type, Class<C> collectionType);

For example, I have a Collection<?> which contains Strings, and some other objects. I want a List<String> from it:

Collection<?> allObjects = getAllObjects();
List<String> stringObjects = objectsOfType(allObjects, String.class, ArrayList.class);

It all works out in the end. objectsOfType will return allObjects if it is an ArrayList containing only Strings, and will otherwise create a new ArrayList<String> and populate it with the String elements of allObjects. I would need to add another argument to allow any List to be returned since ArrayList is more specific than necessary for the return value, because the class must have a constructor. But that's not the problem.

The problem is a compiler warning on the invocation of objectsOfType, and I can't figure out why:

Type safety: Unchecked invocation objectsOfType(Collection<capture#1-of ?>, Class<String>, Class<ArrayList>) of the generic method objectsOfType(Collection<?>, Class<T>, Class<C>) of type CollectionUtilities

What's causing this warning, and how can I fix it? As far as I can tell, everything is safe. The implementation of the method itself is not in question. That doesn't raise any warnings. Only its invocation is the problem.

Upvotes: 2

Views: 85

Answers (1)

Mike Strobel
Mike Strobel

Reputation: 25623

ArrayList.class is not assignable to Class<C>, i.e., Class<List<String>> in a strict/checked context, so passing this argument requires an unchecked assignment, hence the warning. The type of ArrayList.class is simply Class<ArrayList>, where ArrayList is a raw type. There is no way to use the .class operator to specify the class ArrayList<String>.

Do you really need the Class<C> argument in the signature? What if you replaced it with a factory callback (e.g., something like Java 8's Supplier<C>)?

Alternatively, just return a List<T> (and use the same List implementation in all cases), simplifying your signature in the process:

public static <T> List<T> objectsOfType(Collection<?> in, Class<T> type);

You could also drop the wildcard and let the compiler infer the source item type:

public static <T, R> List<R> objectsOfType(Collection<T> in, Class<R> type);

Note, however, that both of the above are lax with respect to the input collection type (which you may have intended). If you want to be strict, you could limit the input item type to a superclass of the result type:

public static <T> List<T> objectsOfType(Collection<? super T> in, Class<T> type);

Upvotes: 1

Related Questions