【问题标题】:Converting a List<String> to a List<Integer> (or any class that extends Number)将 List<String> 转换为 List<Integer> (或任何扩展 Number 的类)
【发布时间】:2011-05-05 18:29:15
【问题描述】:

我想创建一个非常通用的实用方法来获取任何 Collection 并将其转换为从 Number(Long、Double、Float、Integer 等)扩展的用户可选类的 Collection

我想出了这段代码,它使用 Google 集合来转换集合并返回一个不可变列表。

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

我的代码是否矫枉过正,或者您将如何简化它同时保持它足够通用?

【问题讨论】:

    标签: java generics collections guava


    【解决方案1】:

    我认为这段代码最重要的方面是Function,而不是方法本身。我也认为切换Function 正文中允许的子类没有意义,因为您已经知道在创建Function 时要返回哪种类型的Number。如果给定 BigInteger.class,您的方法也会失败,这也有点问题。

    鉴于此,我要做的是创建一个实用程序类(我们称之为Numbers)并在其上提供方法,每个方法都返回一个Function(可以是一个enum单例)来解析@987654329 @ 作为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。
    • 您只创建您真正想要允许的Functions。如果没有 BigInteger 解析函数,用户就不能调用它,而不是像在你的示例中那样在编译时这样做然后在运行时失败是完全合法的。

    作为旁注,我建议将任何返回 ImmutableList 的方法的返回类型设为 ImmutableList 而不是 List... 它提供了对方法的客户有用的信息。

    编辑:

    如果您真的需要更动态的东西(即,您希望具有某些 Class&lt;T extends Number&gt; 实例的类能够将 Strings 转换为 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 ,那么这与您的原始方法有相同的问题。似乎在很多情况下这都没有用。

    【讨论】:

    • @ColinD:但是如果您的客户不知道他们想要哪种类型的 Number,他们可以使用这些方法(只要他们反过来有一个类令牌) .我喜欢这种方法,但它似乎无法解决与问题相同的情况。
    • 我确实同意,尽管这根本不是泛型的一个很好的应用,因为无论如何每种类型都必须单独处理。
    • @Mark:我的印象是,一般的想法是通过将特定数字类型的类文字直接传递给它来使用它,而不是任何更动态的东西。但是,创建一个给定 Class&lt;T extends Number&gt; 并基于此返回正确的 Function 的方法很容易。
    • @ColinD:我更喜欢 OP 的代码。一个小地图可以很好地摆脱丑陋的 ifs。
    • 我将采用这种方法。我想知道为什么 Google Collections 中还没有这样的方法。
    【解决方案2】:

    为什么不实现几个转换器函数并将它们传递给 Lists.transform() 调用?

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

    所以,你可以写:

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

    如果你想自动处理类型,你可以添加一个transformer factory或者一个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;         
    }
    

    【讨论】:

    • 我不建议将这些函数公开为像 IntegerTransformer 这样的类型。客户端不需要知道实现类型,只要有一个Function&lt;String, Integer&gt; 可以满足他们的需求。客户端也不需要自己实例化它们,因为它们是无状态的并且可以是单例的。
    • 当然。主要思想是从转换器功能中删除所有重复的逻辑。剩下的就是实现细节。例如。我不建议使用枚举来创建单例,除了在这种情况下强制单例是一种矫枉过正。
    • @Eugene Kuleshov:你为什么不推荐它?这无疑是制作单身人士的最佳方式。而且我认为没有任何理由不将它们设为单例,特别是如果您要将每个实例存储在 Map 中。
    • @Eugene:基本上你刚才所说的一切都是错误的。我建议阅读Effective Java,不要发表无法备份的声明。
    • @ColinD 您是否建议创建枚举来制作单例?乔什·布洛赫(Josh Bloch)的书是一本不错的读物,但它反映了作者对语言某些方面的看法,所以我仍然持保留态度。
    【解决方案3】:

    我觉得不错。

    既然你有 Class 令牌,为什么不避免未经检查的演员表,从而抑制警告?

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

    【讨论】:

    • Class.cast() 方法调用比常规转换慢得多。所以,除非万不得已,否则最好不要使用。
    • @Eugene:请提供数据/参考来补充您的反对意见。不走这条路对我来说必须是一个不平凡的性能打击,因为它无需检查即可保证类型安全。类型安全对我来说比微优化重要得多。我怀疑这种转换是否会在实践中导致一点性能差异,除非应用程序的大部分逻辑是转换列表。
    • @Mark 仅仅是因为它是一个方法调用与 INSTANCEOF 字节码。后者被 HotSpot 非常有效地折叠(即执行路径预测)。你可以写一个简单的微基准来验证。
    • @Eugene:实际上你可以,因为你是那个让我失望的人。我不认为你真的知道你在说什么,因此我不明白 d/v。例如,演员 (T) 甚至不会 be 在字节码中,因为它没有被检查(因此在运行时被删除),但你说它会。无论哪种方式,当功能正确性和类型安全在这里更为重要时,您都会担心微优化。如果您认为性能调整更重要,那很好,但不值得投反对票。不像我说的错。
    • @Eugene:是的,它与编程风格有关。而且由于绝对没有证据表明存在性能损失,即使存在性能损失也可以忽略不计,我的方式更好,因为它是类型安全的(或者更确切地说是类型更安全,因为它在发生错误的库代码中失败)
    【解决方案4】:

    您可以使用反射,并执行以下操作:

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

    未经测试,可能很慢。

    【讨论】:

    • 真正的问题是这些类没有实现与valueOf(String) 的接口。如果他们这样做了,你就不需要这些废话了。您可以使用 newInstance 创建一个类型的对象,转换为接口,然后调用它。
    • 如果它实现了一个接口,您将需要一个对象的instance,这会导致其他性能影响。 valueOf 现在是静态的。不幸的是,没有办法拥有一个指定静态方法的接口。
    • 最好使用 Number.class.isAssignableFrom(clazz),因此不会创建新的不必要的实例。
    • @Eugene, @Stefan:实际上,如果您不滥用泛型,这真的完全无关紧要。我们已经从边界知道了T extends Number。
    • 哦,对了。我不知道那种方法,尤金,所以谢谢你。
    猜你喜欢
    • 2010-09-06
    • 2013-08-03
    • 2022-09-30
    • 2012-05-29
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    相关资源
    最近更新 更多