views:

250

answers:

9

In Java, how do I convert List<?> to List<T> using a general purpose method so that I can replace patterns like the following with a single method call:

List untypedList = new ArrayList();  // or returned from a legacy method
List<Integer> typedList = new ArrayList<Integer>();
for (Object item: untypedList)
    typedList.add((Integer)item);

Note that the above code does not generate any type-safety warnings and, ideally, your solution shouldn't generate any such warnings, either.

Will the following solution work provided that list Class<L> has a public default constructor?

public class ListUtil {
    public static <T, L extends List<T>> L typedList(List<?> untypedList, Class<T> itemClass, Class<L> listClass) {
        L list = null;
        try {
            list = listClass.newInstance();
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        }
        for (Object item: untypedList)
            list.add(itemClass.cast(item));
        return list;
    }
}

(Note that listClass.newInstance() throws InstantiationException or IllegalAccessException if an instance of Class<L> does not have a public default constructor. What problems may arise if the method does not properly handle these exceptions?)

Notes:

  • T is the type of each item in the resulting list.
  • L is the type of the list that I wish to create (which extends List<T>).
  • untypedList is the "untyped" input list, effectively the same as List<Object>.
  • itemClass represents the runtime class of T.
  • listClass represents the runtime class of L.
+2  A: 

You have a runtime problem so it should be independent from generics. At runtime, everyting "is an Object" anyway. If you can't instantiate listClass, then you actualy pass an implementation of java.util.List that doesn't offer a (public) empty constructor.

So the solution to your problem is outside this method. Calling it like

 List<String> result = typedList(untypedList, String.class, ArrayList.class);

shouldn't give a runtime error.


Now I have my eclipse at hand. The following code compiles and, no warnings and should fulfill your requirement: convert from an untyped list to a typed list.

public static <T> List<T> typedList(List<?> untypedList, Class<T> itemClass) {
  List<T> list = new ArrayList<T>();
  for (Object item : untypedList) {
    list.add(itemClass.cast(item));  // TODO - handle ClassCastExpception
  }
  return list;
}
Andreas_D
Right, so how can I modify `L extends List<T>` to express that I want `L` to be an implementation of a `List<T>` that has a public default constructor?
Derek Mahar
@Derek - the interesting piece of code is that part, where you actually call this method. Maybe you use `List.class` (an interface) as a third parameter?
Andreas_D
Seems that I was confused by the compiler error. I've never actually run this code, so it may throw an exception depending on the context in which it is run. `Class<T>.newInstance()` throws both `InstantiationException` and `IllegalAccessException`, so all I need is a try/catch block around `Class<T>.newInstance()` that catches those exceptions. I'll modify my question to make this more clear.
Derek Mahar
Andreas, I tried a similar call to `typedList()` and the compiler issues the warning, "Type safety: The expression of type ArrayList needs unchecked conversion to conform to List<Integer>." How can I eliminate this warning? (Note that eliminating this warning was the original motivation for the method.)
Derek Mahar
If the purpose is to eliminate the warning, why don't you use the @SuppressWarnings("unchecked") annotation. If you know the list type, there won't be a runtime error and it will be much faster as regenerate a complete List only to suppress the warning...
Benoit Courtine
Benoit, I want to eliminate the warning, but at the same time I don't wish to delay any potential `ClassCastExceptions` that may occur due to some unexpected element in the list that may not conform to the "cast" type. By explicitly converting the list by casting each individual element separately, I can immediately catch any offending element.
Derek Mahar
Andreas, your solution is the same as that of amofis (http://stackoverflow.com/questions/3548733/how-do-i-convert-from-list-to-listt-in-java-using-generics/3550153#3550153) both of which assume that the input list is an `ArrayList`, which may not be the case.
Derek Mahar
@Derek - that's not true. The input can be any `List` implementation, we just create and return an ArrayList. So the source and target lists may have different types, but that's OK in most cases as we usually code against interfaces.
Andreas_D
Andreas, you are correct, but I prefer the returned list to be of the same type as the input or at least client optional.
Derek Mahar
@Derek - for your next question, please write *all* requirements before we all start looking for a solution. You never told us, that you want input and ouput list of the same type. In your actual edit, you want to convert `List`, and that's what my and amorfis' solution exactly do.
Andreas_D
Andreas, I apologize for not being completely clear in my question. You are correct that my question doesn't explicitly state that I wish the output list type to match the input list type, but `list = listClass.newInstance()` implies this desire. I also agree that your solutions correctly convert the input list to an `ArrayList`.
Derek Mahar
A: 

I don't believe what you are trying to do is possible. This is because of Generics work:

At compile time all ingoing types of a typed list are checked and all outgoing objects are casted to the type of the list - and from that moment we are talking about an untyped "list". Generics are just syntactic sugar, unfortunately.

Jasper
+1  A: 

The Class.newInstance() method throws two checked exceptions IllegalAccessException and InstantiationException. These have to be either caught or declared in the method signature for your method.

For the record, these exceptions are thrown in a variety of situations; e.g.

  • the class defines no no-args constructor
  • the constructor is not visible
  • the class is abstract or an interface
  • the class object denotes an array type, primitive type or the void "type".

This is actually unrelated to the fact that the method is generic, and the associated typing issues.

Stephen C
Yes, I realized this after I asked the question and read the first answer. I've since modified my question to handle these exceptions.
Derek Mahar
+4  A: 

I would use Guava and its Iterables.filter(Iterable,Class) method along with a factory method from the Lists class, like so:

List<?> original = ...;
List<String> typed = Lists.newArrayList(
   Iterables.filter(original, String.class));

This will actually check each object in the original list and the resulting list will contain only those elements that are instances of the given type (String in this case) or a subtype of it. I really don't think it makes sense to have users provide a Class for the resulting List type and try to instantiate it via reflection.

ColinD
How does Guava's `Lists.newArrayList()` avoid the "unchecked conversion" warning from the compiler?
Derek Mahar
There is no unchecked conversion, since `Iterables.filter(Iterable,Class)` returns an `Iterable<T>` (where `T` is the type of the `Class` passed to it). It can do this safely since it ensures that the resulting `Iterable` will only return objects that are actually of type `T`. (Of course, there is a suppressed unchecked warning in the library code itself, but you don't have to worry about this.)
ColinD
Good stuff! Guava is clearly a well-designed library. For the moment, though, I'd prefer an approach that doesn't require the use of an external library, but I'll certainly propose to my team that we consider adopting Guava to deal with generic collections.
Derek Mahar
Guava has a lot of great stuff, collections-related code being only one part. I definitely highly recommend it!
ColinD
Keep in mind that this solution will throw away items which are not a String (this may be exactly what you want, but it looks like the intent behind the original question is to throw a ClassCastException).If the method which provides the original list is not behaving as expected (let's say you expect a List<Double> but somehow it puts in a few Integers), you will lose the integers and may run into subtle bugs down the line (versus getting a ClassCastException at this point, which will be easier to trace back to the root cause).
Michael D
@Michael D That is true. You could certainly do a check that the resulting list has the same size as the original if desired, but that would be kind of weird considering the "filtering" aspect is the main point of the Guava method.
ColinD
A: 

What do you want to achieve? Such code:

List l = new ArrayList();
l.add(new Integer(1));
List<Integer> li = l;

just works. It generates warning, but works. However, it is possible that in li you'll have objects that are not instances of Integer. If you want to be sure, use google guava, like ColinD answered.

amorfis
Yes, this works, but one of my original intentions was to properly eliminate this warning.
Derek Mahar
+2  A: 
Michael D
Michael, I like this approach. No type-safety compiler warnings, either!
Derek Mahar
Michael, I was torn between accepting ColinD's answer that suggests using the Guava library (http://stackoverflow.com/questions/3548733/how-do-i-convert-from-list-to-listt-in-java-using-generics/3549071#3549071) and yours, but I prefer your answer because it elegantly solves my problem without introducing a new third-part library. Adopting a new library is a decision which is not entirely mine to make.
Derek Mahar
Michael, out of curiosity, can you think of any way that the method could instantiate a new list that has the same runtime type of list `from`?
Derek Mahar
I would not recommend attempting to reflectively instantiate the new list. It will be very error-prone and will cause you headaches down the line. I obviously don't know the entire scope of your problem, but instead of this I would consider writing a wrapper around the API that is providing you the untyped list, which does a sanity check on the elements of the list, and then returns the list with an unchecked cast. (This is one of the reasons the ability to even put an unchecked cast was introduced ... there are legitimate cases, especially when dealing with pre-generics apis).
Michael D
A: 

Please don't use reflections for things like this!

I would treat it as a case of a transform. Perhaps something like

public static <D, S> transform(
    List<D> dst,
    List<S> src,
    Transformer<? extends D, ? super S> transformer
) { ... }

Use a factory for List<> if you like.

Tom Hawtin - tackline
Tom, can you expand on your example or refer me to a more detailed example of a transformer?
Derek Mahar
A: 

If you don't want the warning, and don't want to use google guava, you have to implement something similar to guava yourself. E.g.:

    private static <T> List<T> typeList(List<?> l, Class<T> klass) {
        List<T> list = new ArrayList<T>();
        for(Object obj : l) {
            if (klass.isAssignableFrom(obj.getClass())) {
                list.add((T) obj);
            }
        }
        return list;
    }

This implementation just ommits elements which are not instances of T, but you can as well throw exception, or do anything else you want.

amorfis
This assumes that `List<?> l` is an `ArrayList` which may not be the case. The client should get to choose the list implementation.
Derek Mahar
There is no such assumption in this code. `l` can be any `List` implementation. However, returned `List` is always `ArrayList`.
amorfis
Yes, but I prefer the returned list to be of the same type as the input or at least client optional.
Derek Mahar
+1  A: 

Guava again, but allows for more control over the conversion if its more complex than a cast or whatnot.

public List<L> convert(List<T> list) {
    return Lists.transform(list,new Function<T,L>() {

        public Object apply(T from) {

            L magic = (L)from;

            /* magic here */

            return magic;
        }});
}
BjornS
What is the missing magic? Would that just copy the elements from the `List<T>` into the `List<L>`?
Derek Mahar
That would be the conversion process to convert from T to L and would be dependant upon the make up of T and L. And yes it takes an input List<T> and returns List<L>. In this instance it simply casts T to L and returns a list of L.
BjornS