如何使用 Java 8 Stream 将数组转换为 HashMap

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41946327/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:16:11  来源:igfitidea点击:

How to convert Array to HashMap using Java 8 Stream

javaarrayshashmapjava-8java-stream

提问by Loc

I am writing a function to convert array to Map using Java 8 Stream.

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

Here is what I wanted

这是我想要的

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).????
}

Valid usages

有效用途

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

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

Invalid usages

无效用法

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

Any helps?

有帮助吗?

Thanks!

谢谢!

回答by Holger

You may use

您可以使用

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

but it will give you a (founded) uncheckedwarning. Your method can't hold the promise to return a correctly typed Map<K, V>for an array of arbitrary objects and, even worse, it will not fail with an exception, but silently return an inconsistent map if you pass in objects of the wrong type.

但它会给你一个(有根据的)未经检查的警告。您的方法不能保证为Map<K, V>任意对象的数组返回正确类型的 a ,更糟糕的是,它不会因异常而失败,但如果您传入错误类型的对象,则会静默地返回不一致的映射。

A cleaner, commonly used, solution is

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

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

This can be compiled without a warning, as the correctness will be checked at runtime. The calling code has to be adapted:

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

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

Besides the need to specify the actual types as class literals, it has the disadvantage of not supporting generic key or value types (as they can't be expressed as Class) and still having no compile-time safety, only a runtime check.

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



It's worth looking at Java?9. There, you will be able to do:

值得一看Java?9。在那里,您将能够:

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

This will create an immutablemap of an unspecified type, rather than a HashMap, but the interesting point is the API.

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

There is a method <K,V> Map.Entry<K,V> entry(K k, V v)which can be combined with
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)to create a map of a variable length (varargs are still limited to 255 parameters, though).

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

You can implement a similar thing:

你可以实现类似的事情:

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

The convenience method(s) ofare implemented the only way, this can be done with type safety: as overloaded methods with different numbers of arguments, like

便利方法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 makes the cut at ten mappings, if you have more, you have to use the ofEntries(entry(k1, v1), …)variant).

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

If you follow this pattern, you should keep your toMapname or use just map, rather than calling at “of”, as you are not writing the Mapinterface.

如果你遵循这个模式,你应该保留你的toMap名字或使用 just map,而不是在“ of”处调用,因为你不是在编写Map接口。

These overloads might not look very elegant, but they solve all problems. You can write the code just as in your question, without specifying Classobjects, but gain compile-time type safety and even rejection of attempts to call it with an odd number of arguments.

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

You have to make a cut at a certain number of parameters, but, as already noted, even varargs do not support unlimited parameters. And the ofEntries(entry(…), …)form isn't so bad for larger maps.

您必须削减一定数量的参数,但是,如前所述,即使是可变参数也不支持无限参数。ofEntries(entry(…), …)对于较大的地图,这种形式还不错。



The collector Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)returns an unspecified map type, which might even be immutable (though it's a HashMapin the current version). If you want to have a guaranty that a HashMapinstance is returned, you have to use Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new)instead.

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

回答by Michael Bar-Sinai

Getting exactly what you want will probably not work for maps whose key type differs from their value type. This is because Java's variable arity declaration (the Object... entriespart) supports only one type.

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

Some options come to mind:

我想到了一些选项:

  1. You could do the checks dynamically and throw an illegal argument exception if the values don't match. But you'll lose the compiler type-checking.

  2. You could define a Pairclass, and play a bit with static import to get almost what you want:

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

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

e.g.:

例如:

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

回答by user_3380739

Here is my idea by JDK 8 stream:

这是我对 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;
    //    });
}

If you don't mind use third-party library AbacusUtil, the code could be simplified to:

如果您不介意使用第三方库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));
}

And I think the most efficient way to do it is by for loop, if you not particularly pursue using Stream API

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

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

回答by nazar_art

You can use something like map literals.
For achieving this you can use a factory method:

您可以使用诸如地图文字之类的东西。
为了实现这一点,您可以使用工厂方法:

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

Finally, you can do something like following:

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

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

Output:

输出:

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

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

I hope that this gives to you the right way.

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

回答by nazar_art

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


}