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

Loc*_*Loc 9 java arrays hashmap java-8 java-stream

我正在编写一个函数,使用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).????
}
Run Code Online (Sandbox Code Playgroud)

有效的用法

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

Map<String, String> map2 = toMap("k1", "v1", "k2", "v2", "k3", "v3");
Run Code Online (Sandbox Code Playgroud)

无效的用法

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2, "k3");
Run Code Online (Sandbox Code Playgroud)

有帮助吗?

谢谢!

Hol*_*ger 10

你可以用

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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");
Run Code Online (Sandbox Code Playgroud)

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


值得一看的是Java 9.在那里,你将能够做到:

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

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

有一种方法<K,V> Map.Entry<K,V> entry(K k, V v)可以与之结合
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)以创建可变长度的映射(但是,varargs仍然限于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));
}
Run Code Online (Sandbox Code Playgroud)

方便方法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));
}   
Run Code Online (Sandbox Code Playgroud)

(Java 9在十个映射中进行切割,如果你有更多,你必须使用ofEntries(entry(k1, v1), …)变体).

如果你遵循这种模式,你应该保留你的toMap名字或使用map,而不是在" of",因为你没有编写Map界面.

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

您必须在一定数量的参数上进行切割,但是,如前所述,甚至varargs也不支持无限制的参数.ofEntries(entry(…), …)对于较大的地图,形式并不是那么糟糕.


收集器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).