ChatGPT解决这个技术问题 Extra ChatGPT

Type safety: Unchecked cast

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

What went wrong?

I came up with a routine to actually check the cast to parameterized HashMap, which eliminates the unchecked cast warning: link I'd say this is the "correct" solution, but whether or not its worth it might be debatable. :)

J
Jon Skeet

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 :)


I'll take Java's reified generics over untyped NSMutableWhatever, which feels like a ten-year leap backwards, any day of the week. At least Java is trying.
Exactly. If you insist on type checking, it can only be done with HashMap and that won't remove the warning since its the same as not type checking the generic types. It's not the end of the world, but annoying that you're caught either suppressing a warning or living with it.
@JonSkeet What's a reified generic?
@SasQ and all others who are wondering should take a look at this question.
c
chelmertz

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.


This was a while ago, but I was looking for an answer on type checking a Set before a cast, and you can't instanceof on a parametrized generic. e.g. if(event.getTarget instanceof Set) You can only type check a generic with a ? and that won't remove the cast warning. e.g. if(event.getTarget instanceof Set)
A
Amarghosh

As the messages above indicate, the List cannot be differentiated between a List<Object> and a List<String> or List<Integer>.

I've solved this error message for a similar problem:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

with the following:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Explanation: The first type conversion verifies that the object is a List without caring about the types held within (since we cannot verify the internal types at the List level). The second conversion is now required because the compiler only knows the List contains some sort of objects. This verifies the type of each object in the List as it is accessed.


you are right my friend. Instead of casting the list, just iterate it and cast each element, the warning will not appear, awesome.
This removed the warning but still I am not confident :P
Yes feels like blindfolding the compiler but not runtime :D So I don't see any difference between this and @SuppressWarnings("unchecked")
That´s awesome! The main difference of using @SupressWarning is that it that use the annotation eliminates the warning from your IDE and code analysis tools but if you are using the -Werror flag compile you will end up with error yet. Using this approach both warnings are fixed.
In baeldung.com/java-warning-unchecked-cast it is explained that a ClassCastException might be thrown anywhere in your code without reference to this offending unchecked cast. The workaround is to move the cast to something that has a very concise ClassCastException and thus can be fixed easier if need be.
D
David M. Karr

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")

-1: a warning should never be accepted. Or suppress these kind of warnings or fix it. There will come the moment where you'll have to many warnings and you won't see the relevant once.
You can't really avoid class cast warnings when casting parameterised generics i.e Map, so this is the best answer for the original question.
D
David Nehme

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.


R
Rabbit

If you really want to get rid of the warnings, one thing you can do is create a class that extends from the generic class.

For example, if you're trying to use

private Map<String, String> someMap = new HashMap<String, String>();

You can create a new class like such

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Then when you use

someMap = (StringMap) getApplicationContext().getBean("someMap");

The compiler DOES know what the (no longer generic) types are, and there will be no warning. This may not always be the perfect solution, some might argue this kind of defeats the purpose of generic classes, but you're still re-using all of the same code from the generic class, you're just declaring at compile time what type you want to use.


A
Andy

Below code causes Type safety Warning

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Workaround

Create a new Map Object without mentioning the parameters because the type of object held within the list is not verified.

Step 1: Create a new temporary Map

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Step 2: Instantiate the main Map

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Step 3: Iterate the temporary Map and set the values into the main Map

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }

J
João Neto

The solution to avoid the unchecked warning:

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

Looks like a hack not a solution.
- The serializable class MyMap does not declare a static final serialVersionUID field of type long :{
@Ulterior then add such a field: stackoverflow.com/questions/2288937/…
J
Jeremy

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");
}

d
davidxxx

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;

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now