我正在处理一些具有创建Map实例的一致模式的代码:
Map<String, String> valuesMap = new HashMap<String, String>();
valuesMap.put("UserName", "Value1");
valuesMap.put("FirstName", "Value2");
valuesMap.put("LastName", "Value3");
valuesMap.put("Email", "Value4");
我认为它可以像这样读取:
Map<String, String> valuesMap = createMap(
"UserName", "Value1",
"FirstName", "Value2",
"LastName", "Value3",
"Email", "Value4"
);
借助以下方法:
private Map<String, String> createMap(String... parameters) {
Map<String, String> valueMap = new HashMap<String, String>();
if (parameters.length % 2 == 0) {
for (int i = 0; i < parameters.length; i+=2){
String key = parameters[i];
String value = parameters[i + 1];
valueMap.put(key, value);
}
} else {
throw new IllegalArgumentException("The parameters to this method must be even in number");
}
return valueMap;
}
但是通过这样做,我失去了在编译时捕获错误的能力。 例如:有人可以轻松地执行以下操作
Map<String, String> valuesMap = createMap(
"UserName", "Value1",
"FirstName", "Value2",
"LastName", // missing value for key
"Email", "Value4"
);
我很想使用更易读的方法,需要你们的建议。
编辑:
---- @Jon Skeet感谢您指出错误。
答案 0 :(得分:4)
尽管有各种各样的评论,我看到这样的辅助方法使用了很多,用于静态初始化,并且通常发现它们比替代方法更简单。当然,它仅在您的键和值类型相同时才有效 - 否则您可以为(例如)最多4个键和4个值提供重载,此时您也可以获得编译时安全性。
您实际上并没有跳过安全性 - 您将编译时安全性推迟到执行时安全性。只要你至少有一个执行该代码的单元测试,你就会发现错误(当你抛出异常时)。根据我的经验,像这样的地图通常在测试中使用,或者作为永不变异的静态最终地图。 (对于后者,考虑使用不可变映射来强制执行缺少变异......)
那就是说,我会重构方法来首先验证计数:
if (parameters.length % 2 != 0) {
throw new IllegalArgumentException("Odd number of keys and values provided");
}
// Now we can just proceed
Map<String, String> valueMap = new HashMap<String, String>();
for (int i = 0; i < parameters.length; i+=2) {
String key = parameters[i];
String value = parameters[i + 1];
valueMap.put(key, value);
}
return valueMap;
请注意,由于您的循环边界检查,之前您的填充代码中存在错误。
答案 1 :(得分:4)
我敦促你考虑这样做:
@SafeVarargs
public static <K,V> HashMap<K,V> hashMap(Map.Entry<K,V>... kvs) {
return map(HashMap::new, kvs);
}
@SafeVarargs
public static <M extends Map<K, V>, K, V> M map(
Supplier<? extends M> supplier, Map.Entry<K, V>... kvs)
{
M m = supplier.get();
for (Map.Entry<K,V> kv : kvs) m.put(kv.getKey(), kv.getValue());
return m;
}
public static <K,V> Map.Entry<K,V> kv(K k, V v) {
return new SimpleImmutableEntry<K, V>(k,v);
}
您的客户端代码如下所示:
Map<Integer, String> map = hashMap(kv(1, "a"), kv(2, "b"));
并且它将是100%类型安全的,因为键值对在静态类型级别表示。
BTW这并不是从根本上依赖于Java 8的功能。这是Java 7的简化版本:
@SafeVarargs
public static <K,V> HashMap<K,V> hashMap(Map.Entry<K,V>... kvs) {
HashMap<K, V> m = new HashMap<>();
for (Map.Entry<K, V> kv : kvs) m.put(kv.getKey(), kv.getValue());
return m;
}
答案 2 :(得分:2)
采用相同类型的长参数列表的方法被认为是不好的做法。
在您的情况下,而不是使用包含您的用户的详细信息的地图。考虑使用成员userName,firstName,lastName和email创建一个类用户。
答案 3 :(得分:2)
如果你考虑一下,可读性和安全性是高度相关的。极其可读的代码是如此透明,任何畸形都会在您的想法表达简单的背景下闪耀。非常安全的代码必须是可读的,因为通过使代码审查变得困难,您最终会在后续维护期间完成错误。
这里没有冲突可以平衡。你要做的任何事情都可以使帮助方法的实现更安全,这将使它的使用更具可读性。
例如,它不必采用字符串,它可以采用成对的字符串(辅助类)。或者它可以检查其输入的健全性。或者,您可以使用问题中显示的put
方法。任何这些替代方案都可以使结果更安全和更具可读性。
“可读”意味着每次看到它时都不必仔细检查模式,对吗?
答案 4 :(得分:1)
这是一个好主意 - 实际上很棒,但你必须小心错误。在您的情况下,如果有奇数条目,您需要大声抱怨。我经常通过尽可能防止程序启动来做到这一点,除非数据被外部化,此时你只想大声抱怨并可能恢复到以前的值,以免破坏生产服务器坏数据。
如果您可以识别名称与&#34;值&#34;你也应该这样做。
我做了很多这样的事情 - 只要记住,无论何时使用这样的代码删除编译时错误检查,你都要承担运行时的责任!
顺便说一句,你还可以进一步改进错误检查和可读性(使用的逗号只是一个建议,你只需选择一些不能进入任何字段的分隔符,逗号,冒号和tildie都是不错的选择):
String[] values=new String[]{
"Name1,Value1",
"Name2,Value2",
...
}
Then to parse:
for(String s:values) {
String[] splitted=s.split(",")
assert(splitted.size == 2)
valuesMap.put(splitted[0],splitted[1])
}
我不断地使用这个模式和字符串数组初始化。您可以包含诸如方法名称之类的东西,以便将数据绑定到反射代码。
这样做的另一个好处是,外部化很容易使运行时配置和外部化成为可能,几乎不需要额外的工作。