简体   繁体   中英

Is it possible to avoid unchecked casting when calling `clone()`?

This is a contrived example to illustrate the problem. I know there are ways around this that don't generate compiler warnings and you can also disable the warning. I'm wondering if this is possible without any of those tricks.

Given this code:

1 public static void main(String[] args) {
2    Map<String,String> map = null;
3
4    HashMap<String,String> hmap;
5
6    if(map instanceof HashMap)
7        hmap = (HashMap<String,String>)map;
8    else
9        hmap = new HashMap<String,String>(map);
10
11   map = (Map<String,String>)hmap.clone();
12
13    Object o = hmap.clone();
14    if(o instanceof Map<?,?>)
15        map = (Map<String,String>)o;
16 }

The code on both lines 11 and 15 generate the compiler warning:

Unchecked cast from Object to Map<String,String>

Line 11 is a little understandable: Object.clone() returns an Object , and there has been no instanceof check prior to casting. The programmer knows that the clone will be a Map<String,String> , but the compiler can't prove it.

Line 15 is puzzling to me, though. Usually, checking the type of a variable using instanceof and then immediately casting it will not generate such a warning. Indeed, replacing the code with non-parameterized classes like this will generate no warnings, on either of these lines of code:

static class A {}
static class B extends A implements Cloneable {
    public Object clone() { return null; }
}
public static void main(String[] args) {
    A a = null;

    B b;

    if(a instanceof B)
        b = (B)a;
    else
        b = new B();

    a = (A)b.clone();

    Object o = b.clone();
    if(o instanceof A)
        a = (A)o;
}

Back to the original code (with the Map<String,String> references), even adding this awkward construction to the end of the code generates a similar warning:

map = (Map<String,String>)hmap.getClass().cast(o);

The warning this time is Unchecked cast from capture#11-of? extends HashMap to Map<String,String> Unchecked cast from capture#11-of? extends HashMap to Map<String,String> . Trying to write:

map = HashMap<String,String>.class.cast(o);

Generates a compiler error because it can't figure out that HashMap<String,String>.class is a static class reference in the same way that eg HashMap.class , so we have to use a reference of the "correct" type to call Class.cast .

Is this something that Java just can't do?

Is this something that Java just can't do?

Yes, it's how it is by design.

Look at java sources (1.8) of HashMap clone method:

@SuppressWarnings("unchecked")
@Override
public Object clone() {
    HashMap<K,V> result;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
    result.reinitialize();
    result.putMapEntries(this, false);
    return result;
}

It uses @SuppressWarnings("unchecked") for the same purposes to suppress warning on super.clone() .

You can not completely avoid it but if your code has a lot of clone() methods you can minimize these warnings by extracting to method like:

@SuppressWarnings("unchecked")
HashMap<String,String> cloneWithoutWarning(HashMap<String,String> map) { return (HashMap<String,String>) map.clone(); } 

Usually, checking the type of a variable using instanceof and then immediately casting it will not generate such a warning.

This is not true. instanceof s have no effect on casting warnings.

This is not a warning about possible ClassCastException from this cast. Unchecked cast means Java cannot do the cast safely. That means cast might pass without ClassCastException yet the type is not a match. This might result in ClassCastException from unexpected place.

This is a real issue in this case. HashMap.clone() returns Object for backwards compatibility. There is no way to tell, if its implementation is type safe. For example:

import java.util.*;
class Main {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap() {
            @Override
            public Object clone() {
                HashMap o = (HashMap)super.clone();
                o.put("x", new Object());
                return o;
            }
        };
        Map<String,String> copy = (Map<String,String>)map.clone(); // will pass
        System.out.println(copy.get("x")); // no cast but fails with ClassCastException
    }
}

clone() is problematic. If possible, just create a new HashMap .

If you have to use clone only use safe casts:

Map<?, ?> copy = (Map<?, ?>)map.clone();
System.out.println((String)copy.get("x")); // explicit cast will fail

Unless you are dealing with legacy code (non-generic) unchecked casts should be considered hacks/workarounds.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM