Преобразование списка <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;
Неподтвержденный и возможный медленный.