Как преобразовать из списка <?> В список <T> в Java с помощью дженериков?
В 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;
}
}
(Обратите внимание, что listClass.newInstance()
выдает InstantiationException
или IllegalAccessException
, если экземпляр Class<L>
не имеет открытого конструктора по умолчанию. Какие проблемы могут возникнуть, если метод неправильно обрабатывает эти исключения?)
Примечания:
-
T
- это тип каждого элемента в результирующем списке.
-
L
- это тип списка, который я хочу создать (который расширяет List<T>
).
-
untypedList
- это "нетипизированный" список ввода, фактически тот же, что и List<Object>
.
-
itemClass
представляет класс выполнения T
.
-
listClass
представляет класс выполнения L
.
Ответы
Ответ 1
Вместо того, чтобы передавать тип списка, который вы хотите создать, почему бы просто не пройти в пустой коллекции <T> что вы хотите заселить? Это дает пользователям вашего api гораздо большую гибкость, поскольку использование конструктора по умолчанию не всегда идеально. (например, возможно, мне нужен набор, в котором я предоставляю ожидаемое количество элементов, или я хочу отсортированный список, где я предоставляю компаратор).
Кроме того, в качестве дополнительной заметки вы всегда должны программировать наиболее общий интерфейс. В этом случае ваш вход должен быть не более конкретным, чем Iterable, и ваш вывод Collection.
Учитывая это, я напишу метод таким образом -
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 Комментарии здесь:
- Если вы действительно можете доверять LegacyApi (или тому, что предоставило вам нетипизированный список), чтобы возвращать вам коллекцию с ожидаемым типом в ней, вы можете просто сделать непроверенный бросок и подавить его. Это должно быть локализовано в наименьшей возможной области. т.е.: создать что-то вроде TypesafeLegacyApiWrapper, который делегирует вызовы LegacyApi.
- Эта сигнатура метода все еще ломается, если у вас есть что-то более сложное. Например, если у вас есть List < List <String → > этот метод не работает.
Ответ 2
Я бы использовал Guava и его Iterables.filter(Iterable, Class) вместе с методом factory из класса Lists
, например:
List<?> original = ...;
List<String> typed = Lists.newArrayList(
Iterables.filter(original, String.class));
Это фактически проверяет каждый объект в исходном списке, и в результате список будет содержать только те элементы, которые являются экземплярами данного типа (String
в этом случае) или подтип этого. Я действительно не думаю, что имеет смысл, чтобы пользователи предоставляли Class
для результирующего типа List
и пытались создать его через отражение.
Ответ 3
У вас есть проблема времени выполнения, поэтому она должна быть независимой от дженериков. Во время выполнения каждый из них "является объектом". Если вы не можете создать экземпляр listClass
, то вы фактически передадите реализацию java.util.List
, которая не предлагает (public) пустой конструктор.
Итак, решение вашей проблемы выходит за рамки этого метода. Вызвать это как
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;
}
Ответ 4
Метод Class.newInstance()
выдает два проверенных исключения IllegalAccessException
и InstantiationException
. Они должны быть либо пойманы, либо объявлены в сигнатуре метода для вашего метода.
Для записи эти исключения выбрасываются в различных ситуациях; например.
- класс не определяет конструктор no-args
- конструктор не отображается
- класс абстрактный или интерфейс
- объект класса обозначает тип массива, примитивный тип или тип void.
Это фактически не связано с тем, что метод является общим и связанными с ним проблемами с типом.
Ответ 5
Guava снова, но позволяет больше контролировать преобразование, если оно более сложное, чем литье или что-то еще.
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;
}});
}
Ответ 6
Я не верю, что то, что вы пытаетесь сделать, возможно. Это связано с работой Generics:
Во время компиляции проверяются все входящие типы типизированного списка, и все исходящие объекты передаются в тип списка, и с этого момента мы говорим о нетипизированном "списке". Дженерики - это просто синтаксический сахар, к сожалению.
Ответ 7
Чего вы хотите достичь? Такой код:
List l = new ArrayList();
l.add(new Integer(1));
List<Integer> li = l;
просто работает. Он генерирует предупреждение, но работает. Однако возможно, что в li
у вас будут объекты, которые не являются экземплярами Integer
. Если вы хотите быть уверенным, используйте google guava, как ответил ColinD.
Ответ 8
Пожалуйста, не используйте отражения для таких вещей!
Я рассматривал бы это как случай преобразования. Возможно, что-то вроде
public static <D, S> transform(
List<D> dst,
List<S> src,
Transformer<? extends D, ? super S> transformer
) { ... }
Используйте factory для List<>
, если хотите.
Ответ 9
Если вы не хотите этого предупреждения и не хотите использовать google guava, вам нужно реализовать что-то похожее на guava. Например:.
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, но вы также можете исключить исключение или сделать что-либо еще, что вам нужно.