Type safety: Unchecked cast

Viewed 461715

In my spring application context file, I have something like:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

In java class, the implementation looks like:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

In Eclipse, I see a warning that says:

Type safety: Unchecked cast from Object to HashMap<String,String>

What went wrong?

10 Answers

The problem is that a cast is a runtime check - but due to type erasure, at runtime there's actually no difference between a HashMap<String,String> and HashMap<Foo,Bar> for any other Foo and Bar.

Use @SuppressWarnings("unchecked") and hold your nose. Oh, and campaign for reified generics in Java :)

Well, first of all, you're wasting memory with the new HashMap creation call. Your second line completely disregards the reference to this created hashmap, making it then available to the garbage collector. So, don't do that, use:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Secondly, the compiler is complaining that you cast the object to a HashMap without checking if it is a HashMap. But, even if you were to do:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

You would probably still get this warning. The problem is, getBean returns Object, so it is unknown what the type is. Converting it to HashMap directly would not cause the problem with the second case (and perhaps there would not be a warning in the first case, I'm not sure how pedantic the Java compiler is with warnings for Java 5). However, you are converting it to a HashMap<String, String>.

HashMaps are really maps that take an object as a key and have an object as a value, HashMap<Object, Object> if you will. Thus, there is no guarantee that when you get your bean that it can be represented as a HashMap<String, String> because you could have HashMap<Date, Calendar> because the non-generic representation that is returned can have any objects.

If the code compiles, and you can execute String value = map.get("thisString"); without any errors, don't worry about this warning. But if the map isn't completely of string keys to string values, you will get a ClassCastException at runtime, because the generics cannot block this from happening in this case.

A warning is just that. A warning. Sometimes warnings are irrelevant, sometimes they're not. They're used to call your attention to something that the compiler thinks could be a problem, but may not be.

In the case of casts, it's always going to give a warning in this case. If you are absolutely certain that a particular cast will be safe, then you should consider adding an annotation like this (I'm not sure of the syntax) just before the line:

@SuppressWarnings (value="unchecked")

You are getting this message because getBean returns an Object reference and you are casting it to the correct type. Java 1.5 gives you a warning. That's the nature of using Java 1.5 or better with code that works like this. Spring has the typesafe version

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

on its todo list.

The solution to avoid the unchecked warning:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

Another solution, if you find yourself casting the same object a lot and you don't want to litter your code with @SupressWarnings("unchecked"), would be to create a method with the annotation. This way you're centralizing the cast, and hopefully reducing the possibility for error.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}

What did I do wrong? How do I resolve the issue?

Here :

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

You use a legacy method that we generally don't want to use since that returns Object:

Object getBean(String name) throws BeansException;

The method to favor to get (for singleton) / create (for prototype) a bean from a bean factory is :

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Using it such as :

Map<String,String> someMap = app.getBean(Map.class,"someMap");

will compile but still with a unchecked conversion warning since all Map objects are not necessarily Map<String, String> objects.

But <T> T getBean(String name, Class<T> requiredType) throws BeansException; is not enough in bean generic classes such as generic collections since that requires to specify more than one class as parameter : the collection type and its generic type(s).

In this kind of scenario and in general, a better approach is not to use directly BeanFactory methods but let the framework to inject the bean.

The bean declaration :

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

The bean injection :

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
Related