Converting a List to a List (or any class that extends Number)

后端 未结 4 1392
梦如初夏
梦如初夏 2021-01-02 07:34

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, Flo

相关标签:
4条回答
  • 2021-01-02 07:59

    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.

    0 讨论(0)
  • 2021-01-02 08:02

    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;         
    }
    
    0 讨论(0)
  • 2021-01-02 08:04

    You could use reflection, and do something like this:

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

    Untested and possible slow.

    0 讨论(0)
  • 2021-01-02 08:11

    Looks good to me.

    Since you have the Class token, why not avoid the unchecked cast and thus suppress warnings?

    retVal = clazz.cast(Double.valueOf(from)); 
    
    0 讨论(0)
提交回复
热议问题