Как я могу преобразовать из списка <?> В список <T>в Java с использованием обобщений? - PullRequest
15 голосов
/ 23 августа 2010

В 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.

Ответы [ 9 ]

13 голосов
/ 23 августа 2010

Я бы использовал Гуава и его Iterables.filter (Iterable, Class) вместе с методом фабрики из класса Lists, например:

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

Это фактически проверит каждый объект в исходном списке, а результирующий список будет содержать только те элементы, которые являются экземплярами данного типа (в данном случае String) или его подтипом. Я действительно не думаю, что имеет смысл предлагать пользователям Class для результирующего типа List и пытаться создавать его с помощью отражения.

8 голосов
/ 23 августа 2010

Вместо того, чтобы передавать тип списка, который вы хотите создать, почему бы просто не передать пустую коллекцию , которую вы хотите заполнить? Это дает пользователям вашего API гораздо больше гибкости, так как использование конструктора по умолчанию не всегда идеален. (например, возможно, мне нужен набор, в котором я предоставляю ожидаемое количество элементов, или я хочу отсортированный список, в котором я предоставляю компаратор).

Кроме того, как примечание, вы всегда должны программировать на максимально общий интерфейс. В этом случае ваши входные данные не должны быть более конкретными, чем 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;
  }

тогда код вызова выглядит так:

<code>  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 >, этот метод не работает.
3 голосов
/ 24 августа 2010

Гуава еще раз, но позволяет лучше контролировать преобразование, если оно более сложное, чем приведение или еще много чего.

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;
        }});
}
2 голосов
/ 23 августа 2010

У вас есть проблема во время выполнения, поэтому она не должна зависеть от обобщений.Во время выполнения каждый «объект» в любом случае.Если вы не можете создать экземпляр 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;
}
1 голос
/ 23 августа 2010

Метод Class.newInstance() выдает два проверенных исключения IllegalAccessException и InstantiationException. Они должны быть либо перехвачены, либо объявлены в сигнатуре метода для вашего метода.

Для справки, эти исключения создаются в различных ситуациях; например, * +1008 *

  • класс не определяет конструктор без аргументов
  • конструктор не виден
  • класс абстрактный или интерфейс
  • объект класса обозначает тип массива, примитивный тип или void "тип".

Это на самом деле не связано с тем фактом, что метод является универсальным, и связанными с ним проблемами при печати.

0 голосов
/ 23 августа 2010

Если вы не хотите получать предупреждение и не хотите использовать Google Guava, вы должны реализовать что-то похожее на Guava самостоятельно. E.g.:

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

0 голосов
/ 23 августа 2010

Пожалуйста, не используйте отражения для подобных вещей!

Я бы отнесся к этому как к случаю преобразования.Возможно, что-то вроде

public static <D, S> transform(
    List<D> dst,
    List<S> src,
    Transformer<? extends D, ? super S> transformer
) { ... }

Используйте фабрику для List<>, если хотите.

0 голосов
/ 23 августа 2010

Чего вы хотите достичь? Такой код:

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

просто работает. Он генерирует предупреждение, но работает. Однако возможно, что в li у вас будут объекты, которые не являются экземплярами Integer. Если вы хотите быть уверены, используйте Google Guava, как ответил ColinD.

0 голосов
/ 23 августа 2010

Я не верю, что вы пытаетесь сделать это возможно. Это из-за работы дженериков:

Во время компиляции проверяются все входящие типы типизированного списка, и все исходящие объекты приводятся к типу списка - и с этого момента мы говорим о нетипизированном «списке». Дженерики - просто синтаксический сахар, к сожалению.

...