您可以使用
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);
}
但是它会给你一个(有根据的)未经检查警告。你的方法无法保证返回一个正确类型的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);
}
这可以在没有警告的情况下编译,因为正确性将在运行时进行检查。调用代码必须进行适应:
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
),并且仍然没有编译时安全性,只有运行时检查。
值得关注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。
有一个方法
<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));
}
方便的方法 of
只有一种实现方式,这可以通过类型安全实现:使用不同数量参数的重载方法,例如
public static <K,V> Map<K,V> of() {
return new HashMap<>();
}
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)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new)
。
for
循环 :) - ZhongYu