在Java中使用字面值初始化一个Map很困难, 正如Java的一贯作风, 代码比较恶心, 比如myMap = new HashMap<String, Object>();
myMap.put("id", 5);
myMap.put("name", "xuqi");
myMap.put("age", 18);
这令强迫症实在难以忍受, 所以我决定研究一下看看有什么”优雅”的解决方案
其他语言的做法
Javascript
var myMap={id:5, name: "xuqi", age: 18};
Python
myMap = {'id': 5, name: 'xuqi', 'age': 18}
C#
var myMap = new Dictionary<string, Object> {{"id", 5}, {"name", "xuqi"}, {"age", 18}};
C++ (就连C++ 都…)
std::map<int, string> int_to_string = {{1, "java"}, {2, "is"}, {3, "pretty"}};
Java中的一些解决办法
双括号初始化法
//这个方法多了一些多余的put很令人不爽 |
二维数组封装法
定义如下函数:static Map<String,String> createStringMap(String[][] entries){
return Stream.of(entries).collect(Collectors.toMap(data -> data[0], data -> data[1]));
}
static Map<String,Integer> createIntegerMap(Object[][] entries){
return Stream.of(entries).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));
}
则可以用如下方式创建Map//创建值为String的Map
Map<String,String> stringMap =
createStringMap(new String[][] {{ "key1", "value1" },{ "key2", "value2" },{ "key3", "value3" }});
//创建值为Integer的Map
Map<String,Integer> intMap =
createIntegerMap(new Object[][] {{ "key1", 1 },{ "key2", 2 },{ "key3", 3 }});
遗憾的是, 因为Java强大的类型擦除特性, 通过这个思路暂时无法写出下面的方法, 除非再加个恶心的Class<T>参数//该方法因为类型擦除无法实现
static <T> Map<String,T> createMap(Object[][] entries)
另外这个方式也没有编译时检查功能, 不太安全。
参数列表封装法
有很多类库都是这个办法,比如Java 9的Map<String, String> unmodifiableMap = Map.of("key1", "value1", "key2", "value2");
自己实现的话,大概可以这样public static <A> Map<String, A> asMap(Object... keysAndValues) {
return new LinkedHashMap<String, A>() {{
for (int i = 0; i < keysAndValues.length - 1; i++) {
put(keysAndValues[i].toString(), (A) keysAndValues[++i]);
}
}};
}
可以这样调用Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));
上面的asMap没有编译期类型安全检查, 而Java9的版本似乎是安全的,但好像参数数量有限制,而且限制的数量还很少(这点我没去确认)
另外这个方式语义也不太好, Map应该是一对对的,不是吗?
目前最优雅的办法-lambda
查了N多资料后,我终于找到了一个优雅的办法, 那就是使用lambda表达式, 客户代码很简单, 而且类型安全
//混合型Map |
实现方法至少需要Java 8u60, 而且在编译时需要加上 -parameters 参数, 在gradle可以加上下面的代码tasks.withType(JavaCompile) {
configure(options) {
options.compilerArgs << '-parameters' << '-Xlint:unchecked'
}
}
这个实现的关键是SerializedLambda
下面是代码中最主要的部分, 获取lambda表达式的参数名
Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); |