【问题标题】:How to convert Array to HashMap using Java 8 Stream如何使用 Java 8 Stream 将 Array 转换为 HashMap
【发布时间】:2017-06-16 05:24:36
【问题描述】:

我正在编写一个使用 Java 8 Stream 将数组转换为 Map 的函数。

这就是我想要的

public static <K, V> Map<K, V> toMap(Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    // TODO
    Arrays.stream(entries).????
}

有效用法

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2);

Map<String, String> map2 = toMap("k1", "v1", "k2", "v2", "k3", "v3");

无效的用法

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2, "k3");

有什么帮助吗?

谢谢!

【问题讨论】:

标签: java arrays hashmap java-8 java-stream


【解决方案1】:

你可以使用

public static <K, V> Map<K, V> toMap(Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return (Map<K, V>)IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new, (m,i)->m.put(entries[i], entries[i+1]), Map::putAll);
}

但它会给你一个(有根据的)unchecked警告。您的方法无法承诺为任意对象数组返回正确类型的Map&lt;K, V&gt;,更糟糕的是,它不会因异常而失败,但如果您传入错误类型的对象,则会默默返回不一致的映射.

一种更清洁、常用的解决方案是

public static <K, V> Map<K, V> toMap(
                               Class<K> keyType, Class<V> valueType, Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new,
                 (m,i)->m.put(keyType.cast(entries[i]), valueType.cast(entries[i+1])),
                 Map::putAll);
}

这可以在没有警告的情况下编译,因为正确性将在运行时检查。调用代码需要修改:

Map<String, Integer> map1 = toMap(String.class, Integer.class, "k1", 1, "k2", 2);
Map<String, String> map2 = toMap(
                           String.class, String.class, "k1", "v1", "k2", "v2", "k3", "v3");

除了需要将实际类型指定为类文字之外,它的缺点是不支持通用键或值类型(因为它们不能表示为Class)并且仍然没有编译时安全性,只有运行时检查。


值得looking at Java 9。在那里,您将能够:

Map<String, Integer> map1 = Map.of("k1", 1, "k2", 2);
Map<String, String>  map2 = Map.of("k1", "v1", "k2", "v2", "k3", "v3");

这将创建一个未指定类型的不可变映射,而不是HashMap,但有趣的是API。

有一种方法 &lt;K,V&gt; Map.Entry&lt;K,V&gt; entry(K k, V v) 可以与
&lt;K,V&gt; Map&lt;K,V&gt; ofEntries(Map.Entry&lt;? extends K,? extends V&gt;... entries) 结合使用来创建可变长度的映射(不过,可变参数仍然限制为 255 个参数)。

你可以实现类似的东西:

public static <K,V> Map.Entry<K,V> entry(K k, V v) {
    return new AbstractMap.SimpleImmutableEntry<>(k, v);
}
public static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) {
    return Arrays.stream(entries)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

便捷方法of 是唯一实现的方法,这可以通过类型安全来完成:作为具有不同数量参数的重载方法,例如

public static <K,V> Map<K,V> of() {
    return new HashMap<>();// or Collections.emptyMap() to create immutable maps
}
static <K,V> Map<K,V> of(K k1, V v1) {
    return ofEntries(entry(k1, v1));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2) {
    return ofEntries(entry(k1, v1), entry(k2, v2));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   

(Java 9 有十个映射,如果有更多,则必须使用 ofEntries(entry(k1, v1), …) 变体)。

如果您遵循此模式,则应保留您的 toMap 名称或仅使用 map,而不是调用“of”,因为您没有编写 Map 接口。

这些重载可能看起来不是很优雅,但它们解决了所有问题。您可以像在您的问题中一样编写代码,而无需指定 Class 对象,但可以获得编译时类型安全性,甚至拒绝使用奇数个参数调用它的尝试。

您必须在一定数量的参数上进行切割,但是,如前所述,即使可变参数也不支持无限参数。 ofEntries(entry(…), …) 表单对于较大的地图来说也不错。


收集器Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) 返回一个未指定的映射类型,它甚至可能是不可变的(尽管在当前版本中它是一个HashMap)。如果您想保证返回 HashMap 实例,则必须改用 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)-&gt;{throw new IllegalArgumentException("duplicate key");}, HashMap::new)

【讨论】:

    【解决方案2】:

    对于键类型与其值类型不同的映射,可能无法准确获取您想要的内容。这是因为 Java 的变量 arity 声明(Object... entries 部分)仅支持一种类型。

    想到一些选项:

    1. 如果值不匹配,您可以动态进行检查并抛出非法参数异常。但是你会失去编译器类型检查。

    2. 您可以定义一个 Pair 类,并使用静态导入来获得几乎您想要的东西:

    例如:

    class Pair<K,V> {
        final K k;
        final V v;
        Pair( K ak, V av) {
            k=ak;
            v=av;
        }
        static <A,B> Pair<A,B> p(A a, B b) {
            return new Pair(a,b);
        }
    }
    
    public class JavaTest8 {
    
        <K,V> Map<K,V> toMap( Pair<K,V>... pairs ) {
            return Arrays.stream(pairs).collect(Collectors.toMap(p->p.k, p->p.v));
        }
    
        public static void main(String[] args) {
            // Usage
            Map<String,Integer> sti = toMap( p("A",1), p("B",2) );
            Map<Integer,Boolean> itb = toMap( p(1,true), p(42,false) );
        }
    }
    

    【讨论】:

    • 谢谢。我仍然更喜欢传递键、值而不是 Pair。
    【解决方案3】:

    这是我对 JDK 8 流的想法:

    public static <K, V> Map<K, V> toMap(final Object... entries) {
        // Requirements:
        // entries must be K1, V1, K2, V2, .... ( even length )
        if (entries.length % 2 == 1) {
            throw new IllegalArgumentException("Invalid entries");
        }
    
        final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));
        IntStream.range(0, entries.length / 2).forEach(i -> map.put((K) entries[i * 2], (V) entries[i * 2 + 1]));
        return map;
    
        // OR: 
        //    return IntStream.range(0, entries.length / 2).boxed().reduce(new HashMap<K, V>(), (m, i) -> {
        //        m.put((K) entries[i * 2], (V) entries[i * 2 + 1]);
        //        return m;
        //    }, (a, b) -> {
        //        a.putAll(b);
        //        return b;
        //    });
    }
    

    如果你不介意使用第三方库AbacusUtil,代码可以简化为:

    public static <K, V> Map<K, V> toMap2(final Object... entries) {
        // Requirements:
        // entries must be K1, V1, K2, V2, .... ( even length )
        if (entries.length % 2 == 1) {
            throw new IllegalArgumentException("Invalid entries");
        }
    
        return Stream.of(entries).split0(2).toMap(e -> (K) e.get(0), e -> (V) e.get(1));
    }
    

    如果你不是特别追求使用 Stream API,我认为最有效的方法是使用 for 循环

    public static <K, V> Map<K, V> toMap3(final Object... entries) {
        // Requirements:
        // entries must be K1, V1, K2, V2, .... ( even length )
        if (entries.length % 2 == 1) {
            throw new IllegalArgumentException("Invalid entries");
        }
    
        final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));
    
        for (int i = 0, len = entries.length; i < len; i++) {
            map.put((K) entries[i], (V) entries[++i]);
        }
    
        return map;
    
        // OR just call the method in AbacusUtil.       
        // return N.asMap(entries);
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用诸如地图文字之类的东西。
      为此,您可以使用工厂方法:

      // Creates a map from a list of entries
      @SafeVarargs
      public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
          LinkedHashMap<K, V> map = new LinkedHashMap<>();
          for (Map.Entry<K, V> entry : entries) {
              map.put(entry.getKey(), entry.getValue());
          }
          return map;
      }
      
      // Creates a map entry
      public static <K, V> Map.Entry<K, V> entry(K key, V value) {
          return new AbstractMap.SimpleEntry<>(key, value);
      }
      

      最后,您可以执行以下操作:

      public static void main(String[] args) {
          Map<String, Integer> map = mapOf(entry("a", 1), entry("b", 2), entry("c", 3));
          System.out.println(map);
      }
      

      输出:

      {a=1, b=2, c=3}

      我希望这能给你正确的方法。

      【讨论】:

      • 谢谢。我仍然更喜欢传递键、值而不是 Map.Entry。
      【解决方案5】:
      public static <K, V, E> Map<K, V> toMap(Function<E, K> toKey, Function<E, V> toValue, E[][] e){
      
              final Map<K, V> newMap = new HashMap<>();
      
              Arrays
                      .stream(e, 0, e.length - 1)
                      .forEach(s ->
                      {
                          if (s[0] != null || s[1] != null)
                              newMap.put(toKey.apply(s[0]), toValue.apply(s[1]));
                      }
                      );
      
              return newMap;
      
      }
      
      
      
      
      public static void main(String[] args) {
      
              Object[][] objects = new Object[10][2];
              objects[0][0] ="Ahmet";
              objects[0][1] =28;
              objects[1][0] ="Mehmet";
              objects[1][1] =18;
              objects[2][0] ="Kemal";
              objects[2][1] =55;
      
      Map<String, Integer> newMap = toMap((Object::toString), (Object v) -> Integer.parseInt(v.toString()), objects);
      
      System.out.println(newMap.get("Ahmet") + " " + newMap.get("Kemal"));
      
      
      }
      

      【讨论】:

        猜你喜欢
        • 2014-07-20
        • 2019-12-06
        • 2016-07-28
        • 2014-07-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多