Converting a List<String> to a List<Integer> (or any class that extends Number)

11,565

Solution 1

I think the most important aspect of this code is the Function as opposed to the method itself. I also don't think it makes sense to switch over the subclasses you allow in the Function body, as you already know what type of Number you want to return at the time the Function is created. It's also slightly problematic that your method fails if given, say, BigInteger.class.

Given this, what I would do is create a utility class (let's call it Numbers) and provide methods on it that each return a Function (which can be an enum singleton) for parsing a String as a specific type of Number. That is:

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

They could each be implemented something like this:

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";
  }
}

This can then be used however users want:

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

This approach has quite a few advantages over yours:

  • Doesn't require any switching in the Function... we know what type of number we're creating and just do that. Faster.
  • Is more flexible, in that each Function can be used wherever... users aren't forced to use it the way your method does (copying the transformed values into an ImmutableList.
  • You only create the Functions you actually want to allow. If there's no BigInteger parsing function, users just can't call that, as opposed to having it be completely legal to do that at compile time and then fail at runtime like in your example.

As a side note, I'd recommend making the return type of any method that returns an ImmutableList be ImmutableList rather than List... it provides information that is useful to clients of the method.

Edit:

If you really need something more dynamic (i.e. you want classes that have an instance of some Class<T extends Number> to be able to transform Strings to that Number type) you could also add a lookup method like:

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

This has the same problems as your original method, though, if there's a Number subclass that you don't provide a Function for. It also doesn't seem like there would be many situations where this would be useful.

Solution 2

Why don't you implement several transformer functions and pass them to Lists.transform() call?

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

So, you could write:

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

If you want to handle types automatically, you can add a transformer factory or a map:

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;         
}
Share:
11,565
Eliseo Soto
Author by

Eliseo Soto

Updated on July 20, 2022

Comments

  • Eliseo Soto
    Eliseo Soto almost 2 years

    I want to create a very generic utility method to take any Collection and convert it into a Collection of a user selectable class that extends from Number (Long, Double, Float, Integer, etc.)

    I came up with this code that uses Google Collections to transform the Collection and to return an Immutable List.

    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);
        }
    

    It can be used like this:

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

    Is my code overkill or how would you simplify it and at the same time keep it generic enough?