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.

当前回答

警告抑制不是解决办法。你不应该在一个语句中执行两层类型转换。

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;
}

其他回答

下面是一个简短的示例,通过使用其他回答中提到的两种策略来避免“unchecked cast”警告。

Pass down the Class of the type of interest as a parameter at runtime (Class<T> inputElementClazz). Then you can use: inputElementClazz.cast(anyObject); For type casting of a Collection, use the wildcard ? instead of a generic type T to acknowledge that you indeed do not know what kind of objects to expect from the legacy code (Collection<?> unknownTypeCollection). After all, this is what the "unchecked cast" warning wants to tell us: We cannot be sure that we get a Collection<T>, so the honest thing to do is to use a Collection<?>. If absolutely needed, a collection of a known type can still be built (Collection<T> knownTypeCollection).

下面示例中的遗留代码接口在StructuredViewer中有一个属性“input”(StructuredViewer是一个树或表小部件,“input”是它背后的数据模型)。这个“输入”可以是任何类型的Java集合。

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    Collection<T> knownTypeCollection = new ArrayList<T>();
    for (Object object : unknownTypeCollection) {
        T aT = inputElementClazz.cast(object);
        knownTypeCollection.add(aT);
        System.out.println(aT.getClass());
    }

    structuredViewer.refresh();
}

当然,如果我们使用错误的数据类型的遗留代码(例如,如果我们将一个数组设置为StructuredViewer的“输入”而不是Java Collection),上面的代码就会给出运行时错误。

调用方法的例子:

dragFinishedStrategy.dragFinished(viewer, Product.class);

在这种特殊情况下,我不会将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);
    }
}

您可以创建如下所示的实用程序类,并使用它来抑制未检查的警告。

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

你可以这样使用它:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

这里有更多关于这个问题的讨论: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

如果我不得不使用一个不支持泛型的API ..我尝试用尽可能少的行将这些调用隔离在包装器例程中。然后我使用SuppressWarnings注释并同时添加类型安全类型强制转换。

这只是个人的喜好,想让事情尽可能的整洁。

不幸的是,这里没有很好的选择。记住,所有这些的目标都是保持类型安全。“Java泛型”提供了一种处理非泛型遗留库的解决方案,在8.2节中有一种特别的方法叫做“空循环技术”。基本上,使不安全施放,并压制警告。然后像这样循环地图:

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

如果遇到意外类型,您将得到一个运行时ClassCastException,但至少它将发生在问题的根源附近。