[英]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.