关于Java:如何处理未经检查的CAST警告?

How do I address unchecked cast warnings?

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

Type safety: Unchecked cast from Object to HashMap

这是对API的调用,我无法控制该API返回对象:

1
2
3
4
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中关闭这些警告。

在我开始编写代码之前,它很简单,但仍然引起了警告:

1
2
3
4
HashMap getItems(javax.servlet.http.HttpSession session) {
  HashMap theHash = (HashMap)session.getAttribute("attributeKey");
  return theHash;
}

当您尝试使用哈希时,其他地方出现了问题,您会收到警告:

1
2
3
4
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.


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

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

例子:

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

没有办法确定Map是否真的应该具有通用参数。你必须事先知道参数应该是什么(否则你会发现什么时候你得到一个ClassCastException)。这就是代码生成警告的原因,因为编译器不可能知道是否安全。


不幸的是,这里没有好的选择。记住,所有这些的目的都是为了保护类型安全。"Java泛型"为处理非一般化的遗留库提供了解决方案,在第8.2节中有一个特别称为"空循环技术"。基本上,进行不安全的强制转换,并抑制警告。然后像这样循环浏览地图:

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

如果遇到意外类型,您将得到运行时类castexception,但至少它将发生在接近问题源的位置。


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

问题是演员表没有检查。所以,你得自己检查一下。不能只使用instanceof检查参数化类型,因为参数化类型信息在运行时不可用,已在编译时清除。

但是,您可以使用instanceof对哈希中的每个项执行检查,这样,您就可以构造一个类型安全的新哈希。你不会挑起任何警告。

多亏了mmyers和esko luontola,我已经参数化了我最初在这里编写的代码,所以它可以包装在某个实用程序类中,并用于任何参数化的哈希图。如果你想更好地理解它并且不太熟悉泛型,我鼓励你查看这个答案的编辑历史。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}

这是很多工作,可能只需要很少的奖励…我不确定我是否会用它。对于人们是否认为它值得,我将不胜感激。另外,我也很欣赏改进建议:除了抛出断言者错误,还有什么更好的方法吗?有什么更好的东西可以扔吗?我应该将其设为检查异常吗?


在Eclipse首选项中,转到Java ->编译器>错误/警告->泛型类型,并检查EDCOX1"0"复选框。

这满足了问题的目的,即

I'd like to avoid Eclipse warnings...

如果不是精神。


您可以创建如下实用程序类,并使用它来禁止未选中的警告。

1
2
3
4
5
6
7
8
9
10
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;
    }
}

您可以按如下方式使用:

1
2
3
4
5
6
import static Objects.uncheckedCast;
...

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

关于这方面的更多讨论如下:http://cliverlias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


这很难,但我现在的想法是:

如果您的API返回对象,那么您将无能为力——无论怎样,您都将盲目地投射该对象。您可以让Java抛出CLSASSCAST异常,或者您可以自己检查每个元素并抛出断言或非法逻辑异常或一些这样的异常,但是这些运行时检查都是等价的。无论在运行时做什么,都必须抑制编译时未选中的强制转换。

我更喜欢盲转换,让JVM为我执行运行时检查,因为我们"知道"API应该返回什么,并且通常愿意假定API可以工作。如果需要的话,可以在演员表上的任何地方使用仿制药。因为您仍然拥有单盲强制转换,所以您实际上没有在那里购买任何东西,但是至少您可以从那里开始使用泛型,这样JVM可以帮助您避免在代码的其他部分中进行盲强制转换。

在这种特殊情况下,您可能会看到对setattribute的调用,并看到类型正在进入,所以在退出时盲目地将类型强制转换为相同的类型并不是不道德的。添加一个引用setattribute的注释并完成它。


在HTTP会话世界中,您不能真正避免强制转换,因为API是以这种方式编写的(只接受并返回Object)。

不过,只要做一点工作,你就可以轻松地避免不受约束的演员阵容。这意味着它将变成一个传统的演员阵容,在出现错误的情况下,给予一个ClassCastException。一个未经检查的异常可能在以后的任何时候变成一个CCE,而不是投射点(这就是为什么它是一个单独的警告)。

将哈希图替换为专用类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,所有内容都将在您编写代码的确切位置进行检查。以后没有意外的ClassCastExceptions


下面是一个简短的例子,它通过使用其他答案中提到的两种策略来避免"未检查的强制转换"警告。

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

  • 对于集合的类型转换,使用通配符?而不是一个通用类型t来确认您确实不知道从遗留代码(Collection unknownTypeCollection)期望得到什么类型的对象。毕竟,这就是"未经检查的演员"警告要告诉我们的:我们不能确定我们得到了一个Collection,所以诚实的做法是使用Collection。如果绝对需要,仍然可以构建已知类型的集合(Collection knownTypeCollection)。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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();
    }

    当然,如果我们使用带有错误数据类型的遗留代码(例如,如果我们将数组设置为StrutDeVIEW的"输入"而不是Java集合),则上面的代码可能会导致运行时错误。

    调用方法的示例:

    1
    dragFinishedStrategy.dragFinished(viewer, Product.class);


    在这种特殊情况下,我不会直接将映射存储到httpsession中,而是将自己的类的实例存储到httpsession中,而该实例又包含一个映射(类的实现细节)。然后您可以确保地图中的元素是正确的类型。

    但是,如果您无论如何都想检查地图内容的类型是否正确,可以使用如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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);
        }
    }


    上面由Esko Luintola给出的答案中的objects.unchecked实用程序功能是避免程序混乱的一个很好的方法。

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

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

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

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

    注:我觉得重要的是,有时警告确实意味着你做了一些错误的事情,比如:

    1
    2
    3
    4
    5
    6
    7
    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

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


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

    1
    2
    //noinspection unchecked
    Map<String, String> myMap = (Map<String, String>) deserializeMap();


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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;
    }


    我可能误解了这个问题(一个例子和一些周围的行很好),但是为什么你不总是使用一个适当的接口(和java5+)?我看不出你为什么要用HashMap来代替Map。事实上,我无法想象有任何理由将变量类型设置为HashMap,而不是Map

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


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

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


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

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

    当你需要的时候会发出警告

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

    也许值得一看

    Java编程语言中的泛型

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


    以这个为例,它比创建一个新的哈希映射快得多,如果它已经是一个哈希映射,但是仍然是安全的,因为每个元素都会根据它的类型进行检查…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @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);
        }


    几乎计算机科学中的每一个问题都可以通过增加一个间接的层次来解决。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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();
    }

    *除了太多的间接性。


    当我覆盖equals()操作时,我可以用一种方法来处理这个问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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中工作(甚至用EDCOX1(2)来编译)


    有两种方法,一种完全避免标记,另一种使用淘气但很好的实用方法。问题是预生成集合…我相信经验法则是:"一次投射一个物体"——这意味着在一个仿制药的世界中使用原始类时,因为你不知道这张地图上是什么??>(事实上,JVM甚至可能发现它甚至不是一张地图!)很明显,当你想到它的时候,你不能投它。如果你有一个mapmap2 then hashsetkeys=(hashset)map2.keyset()不会向您发出警告,尽管这对于编译器来说是一种"信念行为"(因为它可能是一个树集)。但这只是一种信仰行为。ps对于以我的第一种方式迭代"很无聊"和"需要时间"的反对意见,答案是"没有痛苦就没有收获":一个泛型的集合保证包含map.entrys,而不是其他任何东西。你必须为这个保证付款。当系统地使用泛型时,这种支付方式非常漂亮,采用的是符合编码的形式,而不是机器时间!一个学派可能会说,您应该设置Eclipse的设置,以使这种未经检查的强制转换错误,而不是警告。那样的话,你就得用我的第一种方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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;
        }


    }


    只需在铸造前进行打字检查。

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

    对于任何人来说,接收不确定类型的对象是很常见的。许多遗留的"SOA"实现传递了各种您不应该始终信任的对象。(恐怖!)

    edit修改了示例代码一次,以匹配海报的更新,并且根据一些注释,我看到instanceof不能很好地处理泛型。但是,更改检查以验证外部对象似乎可以很好地使用命令行编译器。修订后的示例现已发布。


    如果您确定session.getattribute()返回的类型是hashmap,则不能将类型转换为该确切类型,而只能依赖于检查通用hashmap

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

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


    这会使警告消失…

    1
    2
    3
    4
    5
     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;
    }


    问题在于:

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

    session.getAttribute(...)的结果是一个Object可以是任何东西,但是由于你"知道"它是一个HashMap你只是在不首先检查它的情况下进行铸造。因此,警告。为了迂腐,Java希望在这种情况下,您应该检索结果并验证它与EDCOX1(12)的兼容性。


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

    上面提到的几个"解决方案"都是越界的,为了抑制一个愚蠢的警告,使得代码不可读。