[英]How do I convert from List<?> to List<T> in Java using generics?
在Java中,如何使用通用方法将List<?>
转换为List<T>
以便我可以使用单个方法调用替换以下模式:
List untypedList = new ArrayList(); // or returned from a legacy method
List<Integer> typedList = new ArrayList<Integer>();
for (Object item: untypedList)
typedList.add((Integer)item);
请注意,上面的代码不会生成任何类型安全警告,理想情况下,您的解决方案也不应生成任何此类警告。
如果列表Class<L>
具有公共默认构造函数,以下解决方案是否可行?
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;
}
}
(请注意,如果Class<L>
的实例没有公共默认构造函数,则listClass.newInstance()
会抛出InstantiationException
或IllegalAccessException
。如果方法没有正确处理这些异常,可能会出现什么问题?)
笔记:
T
是结果列表中每个项目的类型。 L
是我想要创建的列表的类型(扩展了List<T>
)。 untypedList
是“无类型”输入列表,实际上与List<Object>
相同。 itemClass
表示T
的运行时类。 listClass
表示L
的运行时类。 我会使用Guava及其Iterables.filter(Iterable,Class)方法以及Lists
类中的工厂方法,如下所示:
List<?> original = ...;
List<String> typed = Lists.newArrayList(
Iterables.filter(original, String.class));
这将实际检查原始列表中的每个对象,结果列表将仅包含给定类型的实例(在本例中为String
)或其子类型的那些元素。 我真的认为让用户为结果List
类型提供Class
并尝试通过反射实例化它是不合理的。
为什么不传入你想要填充的空集合<T>,而不是传入你想要实例化的列表类型? 这为api的用户提供了更大的灵活性,因为使用默认构造函数并不总是理想的。 (例如,也许我想要一个Set,我提供预期数量的元素,或者我想要一个排序列表,我提供比较器)。
另外,作为附注,您应该始终编程到最通用的接口。 在这种情况下,您的输入不需要比Iterable更具体,输出集合。
鉴于此,我会用这种方式编写方法 -
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;
}
那么调用代码看起来像:
public static void main(String[] args) {
List<?> untypedStringList = LegacyApi.getStringList();
List<String> typesafeStringList = typesafeAdd(untypedStringList, new ArrayList<String>(), String.class);
}
2评论在这里:
番石榴再次,但如果它比一个演员或其他更复杂,允许更多的控制转换。
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;
}});
}
您有运行时问题,因此它应该独立于泛型。 在运行时,无论如何,每个“都是一个对象”。 如果你不能实例化listClass
,那么你listClass
传递了一个不提供(公共)空构造函数的java.util.List
实现。
因此,您的问题的解决方案不在此方法之内。 称之为
List<String> result = typedList(untypedList, String.class, ArrayList.class);
不应该给出运行时错误。
现在我手头有食。 以下代码编译,没有警告,应该满足您的要求:从无类型列表转换为类型列表。
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;
}
Class.newInstance()
方法抛出两个已检查的异常IllegalAccessException
和InstantiationException
。 必须在方法的方法签名中捕获或声明它们。
为了记录,这些例外在各种情况下被抛出; 例如
这实际上与该方法是通用的以及相关的键入问题无关。
我不相信你想做的事情是可能的。 这是因为泛型工作:
在编译时,将检查所有输入类型的类型化列表,并将所有传出对象转换为列表类型 - 从那时起,我们将讨论无类型“列表”。 遗憾的是,泛型只是语法糖。
你想达到什么目的? 这样的代码:
List l = new ArrayList();
l.add(new Integer(1));
List<Integer> li = l;
只是工作。 它产生警告,但有效。 但是,有可能在li
你将拥有不是Integer
实例的对象。 如果你想确定,请使用google guava,就像ColinD回答的那样。
请不要对这样的事情使用反射!
我会把它视为变换的一个例子。 也许是这样的
public static <D, S> transform(
List<D> dst,
List<S> src,
Transformer<? extends D, ? super S> transformer
) { ... }
如果您愿意,可以使用List<>
工厂。
如果你不想要警告,并且不想使用谷歌番石榴,你必须自己实现类似于番石榴的东西。 例如:
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;
}
这个实现只是省略了不是T实例的元素,但你也可以抛出异常,或做任何你想做的事情。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.