Question

How do I write a method that takes a parameter of some type T which is an instance of Iterable, as well as a parameter of Class<E>, and return T<E>?

public static <...> ... checkedCast(T iterable, Class<E> clazz) {
    // Check elements and throw ClassCastException if invalid

    @SupressWarning("checked")
    ... cast = (...)iterable;
    return cast;
}

I want to use it like this:

// This should compile
ArrayList<?> a = ...;
ArrayList<String> b = checkedCast(a, String.class);

// So should this
HashSet<Number> c = ...;
Set<Integer> d = checkedCast(c, Integer.class);

// This shouldn't compile
b = checkedCast(a, Integer.class);

// This shouldn't compile
b = checkedCast(c, Integer.class);

// This should throw ClassCastException
checkedCast(a, Integer.class);

I know I can do this using overrides, but this requires me to write an override for every type:

public static <T> Iterable<T> checkedCast(Iterable<?> iterable, Class<T> clazz) {...}
public static <T> List<T> checkedCast(List<?> list, Class<T> clazz) {...}
public static <T> ArrayList<T> checkedCast(ArrayList<?> list, Class<T> clazz) {...}
public static <T> Set<T> checkedCast(Set<?> set, Class<T> clazz) {...}
Was it helpful?

Solution

One of the weaknesses of the Java type system's Generics extension is that how we think about types in the singular doesn't scale to how we think of types in the plural.

In short, Collections of a generic type cannot be safely cast, ever. Build a new list, pull out each type and check it individually, and the return the new list. If you disregard this warning, I'll direct someone to do something like

List<Customer> customers = new ArrayList<>();
customers.add(new Customer(...));
List<Object> customerObjects = checkCast(customers, Object.class);
customerObjects.add(new Order(...));

You have been warned.

OTHER TIPS

See if this works for you. But, people can help you better if you can describe in more detail why you need such a method.

public static
<InputElement, OutputElement extends InputElement,
 InputContainer extends Iterable<InputElement>,
 OutputContainer extends Iterable<OutputElement>>
OutputContainer checkedCast(InputContainer iterable,
                            Class<OutputElement> clazz) {
  @SuppressWarnings("unchecked")
  OutputContainer output = (OutputContainer) iterable;
  return output;
}

This works/matches your requirements - except for throwing a ClassCastException (if you really want that behaviour, you can include it in the checkedCast method yourself):

import java.util.*;

public class CheckedCast {

    public static <GenB, GenA extends GenB, CollA extends List<GenA>> List<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
        return (List<GenB>)iterable;
    }
    public static <GenB, GenA extends GenB, CollA extends Set<GenA>> Set<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
        return (Set<GenB>)iterable;
    }

    static class One {}
    static class Two extends One {}
    static class Three {}

    public static void main(String[] args) {
        ArrayList<Two> test1 = new ArrayList<Two>();
        List<One> test2 = checkedCast(test1, One.class);

        // Shouldn't compile...
        ArrayList<One> aa = checkedCast(test2, One.class);  // output is ArrayList
        List<Two> bb = checkedCast(test2, Three.class);     // Three is not superClass of Two
        ArrayList cc = checkedCast(new HashSet(), Integer.class);           // Set cannot become List
        ArrayList<One> dd = checkedCast(new LinkedList<One>(), One.class);  // ArrayList is not superClass of List
    }

}

Updated to match new requirement: ArrayList xs = checkedCast(new HashSet(), Integer.class) - shouldn't compile

Update: updated to assert returned Collection generic type extends input Collection's generic type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top