views:

83

answers:

3

Hello!

This code is simplified as much as I can from a more complex class structure. In the real code, there were sub-types of the Integer and Double types I use here.

I'm trying to use Java Generics with a type parameter. If the user requests the type of Number.class, we want to combine the List<Integer> list and the List<Double> list into a single list.

While the code works, I cannot get ride of the unchecked cast warning (see the TODO tag). The warning is :

Type safety: Unchecked cast from List<Integer> to Collection<? extends T>

But, if I remove the cast, I get a compile error :

The method addAll(Collection<? extends T>) in the type List<T> is not applicable for the arguments (List<Integer>).

Suggestions welcome, and thanks!

Bill

package com.foo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class Generics1 {

    static final List<Integer> intList = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
    static final List<Double> dblList = new ArrayList<Double>(Arrays.asList(1.1, 2.2, 3.3));

    public static <T extends Number> List<T> getObjects(Class<T> type) {
        List<T> outList = new ArrayList<T>();

        if (type == Number.class) {
            // user asked for everything
            // TODO: unchecked cast warnings here should be fixed
            outList.addAll((Collection<? extends T>) intList);
            outList.addAll((Collection<? extends T>) dblList);
        } else {

            //
            // user asked for subtype of number
            //

            if (Integer.class.isAssignableFrom(type)) {
                for (Integer i : intList) {
                    if (type.isInstance(i)) {
                        T obj = type.cast(i);
                        outList.add(obj);
                    }
                }
            }

            if (Double.class.isAssignableFrom(type)) {
                for (Double d : dblList) {
                    if (type.isInstance(d)) {
                        T obj = type.cast(d);
                        outList.add(obj);
                    }
                }
            }
        }

        return outList;
    }

    public static void main(String[] args) {
        System.out.println ("HI!");
        System.out.println ("integers: " + getObjects(Integer.class));
        System.out.println ("doubles: " + getObjects(Double.class));
        System.out.println ("numbers: " + getObjects(Number.class));
    }

}
A: 

You could add this to your code:

@SuppressWarnings("unchecked")

Here is another SO post that explains "what" that means: http://stackoverflow.com/questions/1129795/what-is-suppresswarnings-unchecked-in-java

And here is another one dealing with conversion of a link that may be useful: http://stackoverflow.com/questions/367626/how-do-i-fix-the-expression-of-type-list-needs-unchecked-conversion

Though with some recoding you could probably make the warning go away completely and not need to be suppressed.

KennyCason
A: 

(previous answer deleted)

Here's a way of doing this with Guava:

@SuppressWarnings("unchecked")
public static <T> List<T> filterAndCollapse(final Class<T> type, Collection<?> a, Collection<?> b) {
    List combined = new ArrayList();

    Predicate<Object> filter = new Predicate<Object>() {
        public boolean apply(Object obj) {
            return type.isInstance(obj);
        }
    };
    combined.addAll(Collections2.filter(a, filter));
    combined.addAll(Collections2.filter(b, filter));
    return combined;
}

//...
filter(Number.class, intList, dblList);

Edit: The fully-type safe way for comparison.

public static <T> List<T> filterAndCollapse(final Class<T> type, Collection<?> a, Collection<?> b) {
    List<T> combined = new ArrayList<T>();

    Predicate<Object> filter = new Predicate<Object>() {
        public boolean apply(Object obj) {
            return type.isInstance(obj);
        }
    };

    Function<Object, T> transform = new Function<Object, T>() {
        public T apply(Object obj) {
            return type.cast(obj);
        }
    };

    combined.addAll(Collections2.transform(Collections2.filter(a, filter), transform));
    combined.addAll(Collections2.transform(Collections2.filter(b, filter), transform));
    return combined;
}

Unfortunately there's no way to filter and transform in one step with Guava, to my knowledge.

Mark Peters
not using generic? List combined = new ArrayList();
nanda
@nanda: Yeah, the reason for that is because the filter will not cast the items to the right type. You can do it, but it takes an extra transform which is runtime overhead, where as this is just a local raw type. I'll add the other back in to compare.
Mark Peters
A: 
    (Class<T> type)
    List<T> outList = new ArrayList<T>();

    if (type == Number.class) {
        // obviously, T==Number here, though the compiler doesn't know that
        // so we do the cast. compiler will still warn. since the cast makes 
        // perfect sense and is obviously correct, we are ok with it.   
        List<Number> numList = (List<Number>)outList;
        numList.addAll( intList);
        numList.addAll( dblList);
    } else {

The better solution, simply

for list in lists
  for item in list 
     if item instance of type
        add item to result
irreputable
Yes, truly I could just use add(), since the addAll() is where I get the compiler warning. If there is no better suggestion for how to supress the warning with addAll(), I will refactor the code to favor add().
Bill O.