如何使用generics从Java中的List 转换为List ?

在Java中,如何使用通用方法将List转换为List以便我可以使用单个方法调用替换以下模式:

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

请注意,上面的代码不会生成任何类型安全警告,理想情况下,您的解决方案也不应生成任何此类警告。

如果列表Class具有公共默认构造函数,以下解决方案是否可行?

 public class ListUtil { public static <T, L extends List> L typedList(List untypedList, Class itemClass, Class 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; } } 

(注意,如果Class的实例没有公共默认构造函数, listClass.newInstance()会抛出InstantiationExceptionIllegalAccessException 。如果方法没有正确处理这些exception,可能会出现什么问题?)

笔记:

  • T是结果列表中每个项目的类型。
  • L是我想要创建的列表的类型(扩展了List )。
  • untypedList是“无类型”输入列表,实际上与List相同。
  • itemClass表示T的运行时类。
  • listClass表示L的运行时类。

为什么不传入你想要填充的空集合,而不是传入你想要实例化的列表类型? 这为api的用户提供了更大的灵活性,因为使用默认构造函数并不总是理想的。 (例如,也许我想要一个Set,我提供了预期的元素数量,或者我想要一个排序列表,我提供了Comparator)。

另外,作为旁注,您应该始终编程到最通用的接口。 在这种情况下,您的输入不需要比Iterable更具体,输出集合。

鉴于此,我会用这种方式编写方法 –

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

那么调用代码看起来像:

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

2评论在这里:

  • 如果你真的可以信任LegacyApi(或任何为你提供无类型列表的东西)只返回一个具有预期类型的​​集合,那么你可以只做一个未经检查的强制转换并禁止它。 这应该局限于可能的最小范围。 即:创建类似TypesafeLegacyApiWrapper的东西,它将调用委托给LegacyApi。
  • 如果你有更复杂的东西,这种方法签名仍然会崩溃。 例如,如果您有List >,则此方法不起作用。

我会使用Guava及其Iterables.filter(Iterable,Class)方法以及Lists类中的工厂方法,如下所示:

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

这将实际检查原始列表中的每个对象,结果列表将仅包含给定类型的实例(在本例中为String )或其子类型的元素。 我真的认为让用户为结果List类型提供Class并尝试通过reflection实例化它是不合理的。

您有运行时问题,因此它应该独立于generics。 在运行时,无论如何,每个“都是一个对象”。 如果你不能实例化listClass ,那么你listClass传递了一个不提供(公共)空构造函数的java.util.List实现。

因此,您的问题的解决方案不在此方法之内。 称之为

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

不应该给出运行时错误。


现在我手头有食。 以下代码编译并且没有警告并且应该满足您的要求:从无类型列表转换为类型列表。

 public static  List typedList(List untypedList, Class itemClass) { List list = new ArrayList(); for (Object item : untypedList) { list.add(itemClass.cast(item)); // TODO - handle ClassCastExpception } return list; } 

番石榴再次,但如果它比演员或其他更复杂,允许更多的控制转换。

 public List convert(List list) { return Lists.transform(list,new Function() { public Object apply(T from) { L magic = (L)from; /* magic here */ return magic; }}); } 

Class.newInstance()方法抛出两个已检查的exceptionIllegalAccessExceptionInstantiationException 。 必须在方法的方法签名中捕获或声明它们。

为了记录,这些例外情况会在各种情况下引发; 例如

  • 该类没有定义no-args构造函数
  • 构造函数不可见
  • 该类是抽象的或接口
  • 类对象表示数组类型,基本类型或void“type”。

这实际上与该方法是通用的以及相关的键入问题无关。

我不相信你想做的事情是可能的。 这是因为generics工作:

在编译时,将检查所有输入类型的类型化列表,并将所有传出对象转换为列表类型 – 从那时起,我们将讨论无类型“列表”。 遗憾的是,generics只是语法糖。

你想达到什么目的? 这样的代码:

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

只是工作。 它产生警告,但有效。 但是,有可能在li你将拥有不是Integer实例的对象。 如果你想确定,请使用google guava,就像ColinD回答一样。

请不要对这样的事情使用reflection!

我会把它视为变换的一个例子。 也许是这样的

 public static  transform( List dst, List src, Transformer transformer ) { ... } 

如果您愿意,可以使用List<>工厂。

如果你不想要警告,并且不想使用谷歌番石榴,你必须自己实现类似于番石榴的东西。 例如:

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

这个实现只是省略了不是T实例的元素,但你也可以抛出exception,或做任何你想做的事情。