Преобразование списка <String>в список <Integer>(или любой класс, расширяющий число) - PullRequest
10 голосов
/ 05 ноября 2010

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

Iпридумали этот код, который использует 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);

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

Ответы [ 4 ]

7 голосов
/ 05 ноября 2010

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

Учитывая это, я хотел бы создать вспомогательный класс (назовем его Numbers) и предоставить для него методы, каждый из которых возвращает 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, будет ImmutableList вместо List ... он предоставляет информацию, полезную для клиентов метода.

Edit:

Если вам действительно нужно что-то более динамичное (т.е. вы хотите, чтобы классы с экземпляром некоторого Class<T extends Number> могли преобразовать String s в этот 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 голосов
/ 05 ноября 2010

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

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

Итак, вы можете написать:для автоматической обработки типов можно добавить фабрику трансформаторов или карту:

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;         
}
0 голосов
/ 05 ноября 2010

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

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

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

0 голосов
/ 05 ноября 2010

выглядит хорошо для меня.

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

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