简体   繁体   中英

How do I convert from List<?> to List<T> in Java using generics?

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 .

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.

Rather than passing in the type of the list you want to instantiate, why not just pass in the empty Collection<T> that you want populated? This gives the users of your api much more flexibility, as using the default constructor is not always ideal. (for example, maybe I want a Set where I provide the expected number of elements, or I want a sorted list where I provide the Comparator).

Also, as a side note, you should always program to the most generic interface possible. In this case, your input need be nothing more specific than an Iterable, and your output a Collection.

Given this, I would write the method this way --

  public static <T, C extends Collection<T>> C typesafeAdd(Iterable<?> from, C to, Class<T> listClass) {
    for (Object item: from) {
      to.add(listClass.cast(item));
    }
    return to;
  }

then the calling code looks like:

  public static void main(String[] args) {
    List<?> untypedStringList = LegacyApi.getStringList();
    List<String> typesafeStringList = typesafeAdd(untypedStringList, new ArrayList<String>(), String.class);
  }

2 Comments here:

  • If you can really trust LegacyApi (or whatever provided you the untyped List) to only return to you a collection with the expected type in it, then you can just do an unchecked cast and suppress it. This should be localized to the smallest scope possible. ie: create something like TypesafeLegacyApiWrapper which delegates calls to LegacyApi.
  • This method signature still breaks down if you have anything more complicated. For example if you have a List<List<String>> this method does not work.

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;
        }});
}

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;
}

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; eg

  • 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.

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.

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.

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.

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

    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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM