Как преобразовать из списка <?> В список <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, но вы также можете исключить исключение или сделать что-либо еще, что вам нужно.