667

Eclipse 给我以下形式的警告:

类型安全:从 Object 到 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.
4

23 回答 23

592

当然,显而易见的答案是不要进行未经检查的演员表。

如果绝对有必要,那么至少尝试限制@SuppressWarnings注释的范围。根据它的Javadocs,它可以处理局部变量;这样,它甚至不会影响整个方法。

例子:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

没有办法确定是否Map真的应该有泛型参数<String, String>。你必须事先知道参数应该是什么(或者当你得到 a 时你会发现ClassCastException)。这就是代码生成警告的原因,因为编译器不可能知道是否安全。

于 2009-02-03T22:07:09.120 回答
185

不幸的是,这里没有很好的选择。请记住,所有这些的目标是保持类型安全。“ Java 泛型”提供了一种处理非泛型遗留库的解决方案,在第 8.2 节中有一个特别称为“空循环技术”的方法。基本上,进行不安全的演员表,并抑制警告。然后像这样循环遍历地图:

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

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

于 2009-02-03T22:46:42.787 回答
114

哇; 我想我找到了自己问题的答案。我只是不确定它是否值得!:)

问题是没有检查演员表。所以,你必须自己检查。您不能只使用 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 之外,我还能做些什么更好的事情?有更好的东西我可以扔吗?我应该将其设为已检查的异常吗?

于 2009-02-03T22:35:01.937 回答
53

在 Eclipse Preferences 中,转到 Java->Compiler->Errors/Warnings->Generic types 并选中Ignore unavoidable generic type problems复选框。

这满足了问题的意图,即

我想避免 Eclipse 警告...

如果不是精神。

于 2011-10-21T17:40:14.880 回答
27

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

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

于 2009-02-03T22:54:33.597 回答
23

这东西很难,但这是我目前的想法:

如果您的 API 返回 Object,那么您将无能为力——无论如何,您将盲目地转换对象。您让 Java 抛出 ClassCastExceptions,或者您可以自己检查每个元素并抛出 Assertions 或 IllegalArgumentExceptions 等,但这些运行时检查都是等效的。无论您在运行时做什么,都必须抑制编译时未经检查的强制转换。

我只是更喜欢盲投并让 JVM 为我执行运行时检查,因为我们“知道”API 应该返回什么,并且通常愿意假设 API 有效。如果需要,请在演员表上方的任何地方使用泛型。你并没有真正在那里购买任何东西,因为你仍然有单一的盲投,但至少你可以从那里开始使用泛型,这样 JVM 可以帮助你避免在其他代码片段中进行盲投。

在这种特殊情况下,大概您可以看到对 SetAttribute 的调用并看到类型正在进入,因此在退出时将类型盲转换为相同并不是不道德的。添加引用 SetAttribute 的注释并完成它。

于 2011-03-01T23:04:58.450 回答
17

这是一个简短的示例,通过采用其他答案中提到的两种策略来避免“未经检查的演员表”警告。

  1. 在运行时将感兴趣类型的类作为参数传递 ( Class<T> inputElementClazz)。然后你可以使用:inputElementClazz.cast(anyObject);

  2. 对于 Collection 的类型转换,请使用通配符 ? 而不是泛型类型 T 来承认您确实不知道从遗留代码中期望什么样的对象(Collection<?> unknownTypeCollection)。毕竟,这就是“未经检查的演员表”警告想要告诉我们的:我们不能确定我们得到 a Collection<T>,所以诚实的做法是使用 a Collection<?>。如果绝对需要,仍然可以构建已知类型的集合(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 集合),上述代码可能会产生运行时错误。

调用方法示例:

dragFinishedStrategy.dragFinished(viewer, Product.class);
于 2015-08-13T10:57:01.317 回答
13

在 HTTP Session 世界中,您无法真正避免强制转换,因为 API 是这样编写的(仅获取和返回Object)。

不过,只要做一些工作,您就可以轻松避免未经检查的演员表。这意味着它将变成一个传统的演员ClassCastException表,在发生错误时给予那里的权利)。未经检查的异常可能会在以后的任何时候变成 aCCE而不是强制转换的点(这就是它是单独警告的原因)。

将 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>所有内容都将在您编写代码的确切位置进行检查。以后不出意外ClassCastExceptions

于 2009-02-04T16:59:20.037 回答
10

在 Android Studio 中,如果您想禁用检查,您可以使用:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
于 2017-09-01T20:22:35.333 回答
8

在这种特殊情况下,我不会将 Maps 直接存储到 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);
    }
}
于 2009-02-03T23:19:04.047 回答
8

Esko Luontola 上述答案中的 Objects.Unchecked 实用程序函数是避免程序混乱的好方法。

如果您不想在整个方法上使用 SuppressWarnings,Java 会强制您将其放在本地。如果您需要对成员进行强制转换,则可能会导致如下代码:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

使用该实用程序要干净得多,而且您在做什么仍然很明显:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

注意: 我觉得重要的是要补充一点,有时警告真的意味着你做错了,比如:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

编译器告诉您的是,在运行时不会检查此转换,因此在您尝试访问通用容器中的数据之前不会引发运行时错误。

于 2011-08-11T13:53:27.977 回答
6

警告抑制不是解决方案。您不应该在一个语句中进行两级转换。

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;
}
于 2015-03-09T23:43:39.510 回答
2

快速猜测一下,如果您发布您的代码可以肯定地说,但您可能已经做了一些类似的事情

HashMap<String, Object> test = new HashMap();

当你需要做的时候会产生警告

HashMap<String, Object> test = new HashMap<String, Object>();

可能值得一看

Java 编程语言中的泛型

如果您不熟悉需要做什么。

于 2009-02-03T22:04:45.647 回答
2

我可能误解了这个问题(一个例子和几行周围的行会很好),但你为什么不总是使用适当的接口(和 Java5+)?我看不出你为什么要转换为 aHashMap而不是 a Map<KeyType,ValueType>。事实上,我无法想象有任何理由将变量的类型设置为HashMap而不是Map.

为什么来源是Object?它是遗留集合的参数类型吗?如果是这样,请使用泛型并指定所需的类型。

于 2009-02-03T22:05:08.547 回答
2

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

这只是个人喜好,以保持事物尽可能整洁。

于 2009-02-03T22:47:49.523 回答
2

拿这个来说,它比创建一个新的 HashMap 快得多,如果它已经是一个,但仍然安全,因为每个元素都会根据它的类型进行检查......

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }
于 2010-10-14T09:28:19.927 回答
1

只需在投射之前对其进行类型检查。

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

对于任何询问的人,收到您不确定类型的对象是很常见的。大量遗留的“SOA”实现传递了您不应该总是信任的各种对象。(恐怖!)

编辑更改了一次示例代码以匹配海报的更新,并在一些评论之后我发现 instanceof 不能很好地与泛型配合使用。但是,更改检查以验证外部对象似乎与命令行编译器配合得很好。修订后的示例现已发布。

于 2009-02-03T22:16:34.250 回答
1

几乎计算机科学中的每个问题都可以通过添加间接级别*或其他东西来解决。

因此,引入一个比 a 更高级别的非泛型对象Map。没有上下文,它看起来不会很有说服力,但无论如何:

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

*除了太多的间接级别。

于 2009-02-04T15:46:45.503 回答
1

这是我在覆盖equals()操作时处理此问题的一种方法。

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

这似乎在 Java 8 中工作(甚至用 编译-Xlint:unchecked

于 2015-01-30T00:15:32.163 回答
0

如果您确定 session.getAttribute() 返回的类型是 HashMap ,那么您不能将类型转换为该确切类型,而仅依赖于检查通用 HashMap

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

然后 Eclipse 会发出意外警告,但这当然会导致难以调试的运行时错误。我只在非操作关键的上下文中使用这种方法。

于 2010-06-12T12:52:52.917 回答
0

两种方法,一种完全避免标签,另一种使用顽皮但很好的实用方法。
问题是预泛化的集合......
我相信经验法则是:“一次投射一件事” - 这意味着在泛化世界中尝试使用原始类时,因为你不知道什么在这个 Map<?, ?> 中(实际上 JVM 甚至可能会发现它甚至不是一个 Map!),当你​​想到它时,很明显你不能转换它。如果你有一个 Map<String, ?> map2 那么 HashSet<String> keys = (HashSet<String>)map2.keySet() 不会给你一个警告,尽管这对编译器来说是一种“信仰行为”(因为它可能会变成一个 TreeSet)......但这只是一个单一的信仰行为。

PS反对以我的第一种方式进行迭代“很无聊”并且“需要时间”,答案是“没有痛苦就没有收获”:保证泛化集合包含 Map.Entry<String, String>s,什么都没有别的。您必须为此担保付费。当系统地使用仿制药时,这种付款方式很漂亮,采用编码合规性的形式,而不是机器时间!
一种思想流派可能会说,您应该设置 Eclipse 的设置以产生这种未经检查的强制转换错误,而不是警告。在这种情况下,您将不得不使用我的第一种方式。

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}
于 2011-03-21T19:09:52.330 回答
-1

这使警告消失...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}
于 2010-08-23T20:46:37.900 回答
-4

解决方案:在 Eclipse 中禁用此警告。不要@SuppressWarnings 它,完全禁用它。

上面介绍的几个“解决方案”是不合时宜的,为了抑制愚蠢的警告而使代码不可读。

于 2013-02-28T20:27:30.643 回答