首页 文章

如何处理未经检查的投射警告?

提问于
浏览
532

Eclipse正在向我发出以下形式的警告:

类型安全:取消选中从Object转换为HashMap

这是来自对我无法控制的API的调用返回Object:

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

不过,我已经找到了消除这个的好办法 . 我可以将涉及的单行提取到一个方法中,并将 @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.

24 回答

  • 484

    在这种特殊情况下,我不会直接将Maps存储到HttpSession中,而是将我自己的类的实例存储,而后者又包含一个Map(类的实现细节) . 然后,您可以确保 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);
        }
    }
    
  • 1

    问题出在这里:

    ... = (HashMap<String, String>)session.getAttribute("attributeKey");
    

    session.getAttribute(...) 的结果是 object 可能是任何东西,但是因为你"know"它是 HashMap<String, String>instanceofinstanceof 的兼容性 .

  • 2

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

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

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

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

    然后,Eclipse将会出现警告,但当然这会导致运行时错误,这些错误很难调试 . 我只在非操作关键的上下文中使用这种方法 .

  • 10

    拿这个,它比创建一个新的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);
        }
    
  • 20

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

    //noinspection unchecked
    Map<String, String> myMap = (Map<String, String>) deserializeMap();
    
  • -3

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

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

  • 49

    这是一个缩短的 example that avoids the "unchecked cast" warning ,采用其他答案中提到的两个策略 .

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

    • 对于Collection的类型转换,请使用通配符?而不是泛型类型T,以确认您确实不知道遗留代码( Collection<?> unknownTypeCollection )期望的对象类型 . 毕竟,这就是"unchecked cast"警告要告诉我们的:我们不能确定我们得到 Collection<T> ,所以诚实的做法是使用 Collection<?> . 如果绝对需要,仍然可以构建已知类型的集合( Collection<T> knownTypeCollection ) .

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

    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);
    
  • 0

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

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

    当你需要时会产生警告

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

    它可能值得一看

    Generics in the Java Programming Language

    如果你不熟悉需要做什么 .

  • 1

    两种方式,一种是完全避免标签,另一种是使用顽皮但很好的实用方法 .
    问题是预先通用的集合......
    我相信经验法则是:"cast objects one thing at a time" - 这意味着当试图在泛化世界中使用原始类时,这是因为你甚至不是一个 Map !),当你想到它时你很明显无法投射它 . 如果你有一个Map <String,?> map2那么HashSet <String> keys =(HashSet <String>)map2.keySet()不会给你一个警告,尽管这是编译器的"act of faith"(因为它可能会结果成为一个树集)...但它只是一种信仰行为 .

    PS反对我在第一种方式迭代"is boring"和"takes time",答案是"no pain no gain":保证泛化集合包含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;
        }
    
    
    }
    
  • 0

    如果我必须使用不支持泛型的API ..我尝试孤立这些调用包装程序尽可能少的行调用 . 然后我使用SuppressWarnings注释并同时添加类型安全转换 .

    这只是个人喜欢保持尽可能整洁的东西 .

  • 2

    您可以创建如下所示的实用程序类,并使用它来禁止取消选中警告 .

    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

  • 12

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

    因此,引入一个非通用对象,它具有更高级别的 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();
    }
    

    *除了太多的间接水平 .

  • 1

    当然,显而易见的答案是不进行未经检验的演员 .

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

    例:

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

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

  • 2

    在你施放它之前,只需要检查它 .

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

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

    EDIT 更改了示例代码一次以匹配海报's updates, and following some comments I see that instanceof doesn' t与泛型很好地匹配 . 但是,更改检查以验证外部对象似乎与命令行编译器一起使用 . 修订后的例子现已发布 .

  • -1

    不幸的是,这里没有很好的选择 . 请记住,所有这一切的目标是保持类型安全 . 第8.2节中的“Java Generics " offers a solutions for dealing with non-genericized legacy libraries, and there is one in particular called the "空循环技术” . 基本上,进行不安全的转换,并禁止警告 . 然后循环遍历 Map ,如下所示:

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

    如果遇到意外类型,您将获得运行时ClassCastException,但至少它会发生在问题源附近 .

  • 2

    这使得警告消失了......

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

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

    如果您不希望在整个方法上使用SuppressWarnings,Java会强制您将其放在本地方法上 . 如果您需要对成员进行强制转换,则可以生成如下代码:

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

    使用该实用程序要清晰得多,而且您正在做的事情仍然很明显:

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

    NOTE: 我觉得重要的是补充说,有时候警告确实意味着你做错了,比如:

    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
    

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

  • 24

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

    上面提到的一些“解决方案”是脱节的,使代码难以理解,以抑制愚蠢的警告 .

  • 8

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

    问题是没有检查演员表 . 所以,你必须自己检查一下 . 您不能只使用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之外,我还能做些什么吗?我可以抛出更好的东西吗?我应该把它作为一个检查过的例外吗?

  • 6

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

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

    我只是喜欢盲目转换,让JVM为我执行运行时检查,因为我们“知道”API应该返回什么,并且通常愿意假设API有效 . 如果需要,可以在演员阵容上方的任何地方使用泛型 . 你并没有真正购买任何东西,因为你仍然有单盲,但至少你可以从那里使用泛型,所以JVM可以帮助你避免代码中其他部分的盲目转换 .

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

  • 129

    在HTTP会话世界中,您无法真正避免强制转换,因为API是以这种方式编写的(仅接收并返回 Object ) .

    但是,通过一些工作,你可以轻松地避免未经检查的演员阵容 . 这意味着它将变成一个传统的演员,在发生错误的情况下在那里给出一个 ClassCastException ) . 未经检查的异常可能会在以后的任何时候转变为 CCE 而不是转换点('s the reason why it'是一个单独的警告) .

    用专用类替换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 .

  • 111

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

    这满足了问题的意图,即

    我想避免Eclipse警告......

    如果不是精神 .

  • 5

    这是我在覆盖 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 编译)

相关问题