Преобразование списка <String> в список <Integer> (или любой класс, который расширяет число)

Я хочу создать очень общий метод утилиты, чтобы взять любую коллекцию и преобразовать ее в коллекцию выбираемого пользователем класса, которая простирается от числа (Long, Double, Float, Integer и т.д.).

Я придумал этот код, который использует Google Collections для преобразования коллекции и возврата неизменяемого списка.

import java.util.List;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
     * Takes a {@code List<String>} and transforms it into a list of the
     * specified {@code clazz}.
     * 
     * @param <T>
     * @param stringValues
     *            the list of Strings to be used to create the list of the
     *            specified type
     * @param clazz
     *            must be a subclass of Number. Defines the type of the new List
     * @return
     */
    public static <T extends Number> List<T> toNumberList(List<String> stringValues, final Class<T> clazz) {
        List<T> ids = Lists.transform(stringValues, new Function<String, T>() {
            @SuppressWarnings("unchecked")
            @Override
            public T apply(String from) {
                T retVal = null;
                if (clazz.equals(Integer.class)) {
                    retVal = (T) Integer.valueOf(from);
                } else if (clazz.equals(Long.class)) {
                    retVal = (T) Long.valueOf(from);
                } else if (clazz.equals(Float.class)) {
                    retVal = (T) Float.valueOf(from);
                } else if (clazz.equals(Double.class)) {
                    retVal = (T) Double.valueOf(from);
                } else {
                    throw new RuntimeException(String.format("Type %s is not supported (yet)", clazz.getName()));
                }
                return retVal;
            }
        });
        return ImmutableList.copyOf(ids);
    }

Его можно использовать следующим образом:

// Convert List<String> to List<Long>
List<Long> ids = MiscUtils.toNumberList(productIds, Long.class);

Является ли мой код излишним или как вы его упростите и в то же время сохраните его достаточно общим?

Ответ 1

Я думаю, что наиболее важным аспектом этого кода является Function, в отличие от самого метода. Я также не думаю, что имеет смысл переключать подклассы, которые вы разрешаете в теле Function, так как вы уже знаете, какой тип Number вы хотите вернуть в момент создания Function. Это также немного проблематично, что ваш метод выходит из строя, если задано, скажем, BigInteger.class.

Учитывая это, я хотел бы создать класс утилиты (назовите его Numbers) и предоставить на нем методы, которые возвращают a Function (который может быть enum singleton) для синтаксического анализа String как особый тип Number. То есть:

public class Numbers {
  public static Function<String, Integer> parseIntegerFunction() { ... }
  public static Function<String, Long> parseLongFunction() { ... }
  ...
}

Каждый из них может быть реализован примерно так:

public static Function<String, Integer> parseIntegerFunction() {
  return ParseIntegerFunction.INSTANCE;
}

private enum ParseIntegerFunction implements Function<String, Integer> {
  INSTANCE;

  public Integer apply(String input) {
    return Integer.valueOf(input);
  }

  @Override public String toString() {
    return "ParseIntegerFunction";
  }
}

Затем это можно использовать, однако пользователи хотят:

List<String> strings = ...
List<Integer> integers = Lists.transform(strings, Numbers.parseIntegerFunction());

Этот подход имеет несколько преимуществ перед вашим:

  • Не требует переключения в Function... мы знаем, какой тип числа мы создаем, и просто делаем это. Быстрее.
  • Более гибкий, поскольку каждый Function можно использовать везде, где... пользователи не вынуждены использовать его так, как ваш метод (копирование преобразованных значений в ImmutableList.
  • Вы создаете только Function, который вы действительно хотите разрешить. Если нет функции разбора BigInteger, пользователи просто не могут это назвать, в отличие от того, что она полностью законна для этого во время компиляции, а затем не выполняется во время выполнения, как в вашем примере.

В качестве побочного примечания я бы рекомендовал сделать возвращаемый тип любого метода, который возвращает ImmutableList be ImmutableList, а не List... он предоставляет информацию, полезную для клиентов метода.

Edit:

Если вам действительно нужно что-то более динамичное (т.е. вы хотите, чтобы классы, у которых есть экземпляр некоторого Class<T extends Number>, чтобы иметь возможность преобразовать String в этот тип Number), вы также можете добавить метод поиска, например:

public static <T extends Number> Function<String, T> parseFunctionFor(Class<T> type) {
  // lookup the function for the type in an ImmutableMap and return it
}

Это имеет те же проблемы, что и исходный метод, если есть подкласс Number, который вы не предоставляете Function для. Также не кажется, что было бы много ситуаций, когда это было бы полезно.

Ответ 2

Почему бы вам не реализовать несколько функций трансформатора и передать их вызову List.transform()?

    public class IntegerTransformer extends Function<String, Integer>() {
        public Integer apply(String from) {
            return Integer.valueOf(from);
        }
    }

Итак, вы можете написать:

Lists.transform(stringValues, new IntegerTransformer());

Если вы хотите обрабатывать типы автоматически, вы можете добавить трансформатор factory или карту:

static Map<Class,Function<String,?>> transformers = new HashMap<String,?>();
static {
  transformers.put(Integer.class, new IntegerTransformer());
  transformers.put(Integer.class, new LongTransformer());
  ...
}

public static Function<String,?> get(Class c) {
  Function<String,?> transformer = transformers.get(c);
  if(transformer==null) {
    throw new RuntimeException(String.format("Type %s is not supported (yet)", clazz.getName()));
  }
  return transformer;         
}

Ответ 3

Выглядит хорошо.

Поскольку у вас есть токен Class, почему бы не избежать неконтролируемого акта и, таким образом, подавить предупреждения?

retVal = clazz.cast(Double.valueOf(from)); 

Ответ 4

Вы можете использовать отражение и сделать что-то вроде этого:

      Method m = clazz.getDeclaredMethod("valueOf", String.class);
      T str = (T) m.invoke(null, from);
      return str;

Неподтвержденный и возможный медленный.