Eclipse给我一个如下形式的警告:
类型安全:未检查从对象转换到HashMap
这是从一个API调用,我无法控制返回对象:
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
return theHash;
}
如果可能的话,我希望避免使用Eclipse警告,因为理论上它们至少表明存在潜在的代码问题。不过,我还没有找到消除这个问题的好方法。我可以将所涉及的单行单独提取到一个方法中,并向该方法添加@SuppressWarnings(“unchecked”),从而限制忽略警告的代码块的影响。有更好的选择吗?我不想在Eclipse中关闭这些警告。
在我接触代码之前,它更简单,但仍然引起了警告:
HashMap getItems(javax.servlet.http.HttpSession session) {
HashMap theHash = (HashMap)session.getAttribute("attributeKey");
return theHash;
}
问题是在其他地方,当你试图使用散列时,你会得到警告:
HashMap items = getItems(session);
items.put("this", "that");
Type safety: The method put(Object, Object) belongs to the raw type HashMap. References to generic type HashMap<K,V> should be parameterized.
在HTTP Session世界中,您无法真正避免强制转换,因为API就是这样编写的(只接受并返回Object)。
不过,只要稍加努力,你就可以很容易地避免这种未经检查的转换。”这意味着在发生错误时,它将转换为传统的强制转换,并在那里给出ClassCastException)。未经检查的异常可能在以后的任何时候变成CCE,而不是在强制转换的时候(这就是为什么它是一个单独的警告)。
用专用类替换HashMap:
import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Attributes extends AbstractMap<String, String> {
final Map<String, String> content = new HashMap<String, String>();
@Override
public Set<Map.Entry<String, String>> entrySet() {
return content.entrySet();
}
@Override
public Set<String> keySet() {
return content.keySet();
}
@Override
public Collection<String> values() {
return content.values();
}
@Override
public String put(final String key, final String value) {
return content.put(key, value);
}
}
然后转换为那个类,而不是Map<String,String>,所有内容都将在您编写代码的确切位置进行检查。以后不会出现意外的classcastexception。
哇;我想我找到了自己问题的答案。我只是不确定这是否值得!:)
问题是演员没有检查。所以,你必须自己检查。不能只使用instanceof检查参数化类型,因为参数化类型信息在运行时不可用,在编译时已被擦除。
但是,您可以使用instanceof对散列中的每一项执行检查,这样就可以构造一个类型安全的新散列。你不会引起任何警告。
多亏了mmyers和Esko Luontola,我将最初在这里编写的代码参数化了,所以它可以被包装在某个实用程序类中,并用于任何参数化的HashMap。如果您想更好地理解它,并且对泛型不是很熟悉,我建议您查看这个答案的编辑历史。
public static <K, V> HashMap<K, V> castHash(HashMap input,
Class<K> keyClass,
Class<V> valueClass) {
HashMap<K, V> output = new HashMap<K, V>();
if (input == null)
return output;
for (Object key: input.keySet().toArray()) {
if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
Object value = input.get(key);
if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
K k = keyClass.cast(key);
V v = valueClass.cast(value);
output.put(k, v);
} else {
throw new AssertionError(
"Cannot cast to HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", value "+ value +" is not a "+ valueClass.getSimpleName()
);
}
} else {
throw new AssertionError(
"Cannot cast to HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", key "+ key +" is not a " + keyClass.getSimpleName()
);
}
}
return output;
}
工作量很大,报酬可能很少……我不确定我是否会用它。我很感激任何关于人们是否认为它值得的评论。此外,我很感激改进建议:除了抛出AssertionErrors之外,还有什么更好的事情我可以做吗?有更好的东西让我扔吗?我应该让它成为一个受控异常吗?
在这种特殊情况下,我不会将Maps直接存储到HttpSession中,而是将我自己的类的实例存储到HttpSession中,而该类的实例又包含一个Map(类的实现细节)。然后可以确定映射中的元素是正确的类型。
但是如果你想检查Map的内容类型是否正确,你可以使用这样的代码:
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
Object obj = map;
Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
Map<String, String> error = safeCastMap(obj, String.class, String.class);
}
@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
checkMap(map);
checkMapContents(keyType, valueType, (Map<?, ?>) map);
return (Map<K, V>) map;
}
private static void checkMap(Object map) {
checkType(Map.class, map);
}
private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
checkType(keyType, entry.getKey());
checkType(valueType, entry.getValue());
}
}
private static <K> void checkType(Class<K> expectedType, Object obj) {
if (!expectedType.isInstance(obj)) {
throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
}
}
警告抑制不是解决办法。你不应该在一个语句中执行两层类型转换。
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
// first, cast the returned Object to generic HashMap<?,?>
HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");
// next, cast every entry of the HashMap to the required type <String, String>
HashMap<String, String> returingHash = new HashMap<>();
for (Entry<?, ?> entry : theHash.entrySet()) {
returingHash.put((String) entry.getKey(), (String) entry.getValue());
}
return returingHash;
}