Eclipse is giving me a warning of the following form:
Type safety: Unchecked cast from Object to HashMap
This is from a call to
If I have to use an API that doesn't support Generics.. I try and isolate those calls in wrapper routines with as few lines as possible. I then use the SuppressWarnings annotation and also add the type-safety casts at the same time.
This is just a personal preference to keep things as neat as possible.
Two ways, one which avoids the tag completely, the other using a naughty but nice utility method.
The problem is pre-genericised Collections...
I believe the rule of thumb is: "cast objects one thing at a time" - what this means when trying to use raw classes in a genericised world is that because you don't know what is in this Map<?, ?> (and indeed the JVM might even find that it isn't even a Map!), it obvious when you think about it that you can't cast it. If you had a Map<String, ?> map2 then HashSet<String> keys = (HashSet<String>)map2.keySet() does not give you a warning, despite this being an "act of faith" for the compiler (because it might turn out to be a TreeSet)... but it is only a single act of faith.
PS to the objection that iterating as in my first way "is boring" and "takes time", the answer is "no pain no gain": a genericised collection is guaranteed to contain Map.Entry<String, String>s, and nothing else. You have to pay for this guarantee. When using generics systematically this payment, beautifully, takes the form of coding compliance, not machine time!
One school of thought might say that you should set Eclipse's settings to make such unchecked casts errors, rather than warnings. In that case you would have to use my first way.
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;
}
}
Just typecheck it before you cast it.
Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;
And for anyone asking, it's quite common to receive objects where you aren't sure of the type. Plenty of legacy "SOA" implementations pass around various objects that you shouldn't always trust. (The horrors!)
EDIT Changed the example code once to match the poster's updates, and following some comments I see that instanceof doesn't play nicely with generics. However changing the check to validate the outer object seems to play well with the commandline compiler. Revised example now posted.
This stuff is hard, but here are my current thoughts:
If your API returns Object, then there's nothing you can do -- no matter what, you will be blindly casting the object. You let Java throw ClassCastExceptions, or you can check each element yourself and throw Assertions or IllegalArgumentExceptions or some such, but these runtime checks are all equivalent. You have to suppress the compile time unchecked cast no matter what you do at runtime.
I'd just prefer to blind cast and let the JVM perform its runtime check for me since we "know" what the API should return, and are usually willing to assume that the API works. Use generics everywhere above the cast, if you need them. You aren't really buying anything there since you still have the single blind cast, but at least you can use generics from there on up so the JVM can help you avoid blind casts in other pieces of your code.
In this particular case, presumably you can see the call to SetAttribute and see the type is going in, so just blind-casting the type to same on the way out is not immoral. Add a comment referencing the SetAttribute and be done with it.
Here's one way I handle this when I override the equals()
operation.
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);
}
}
This seems to work in Java 8 (even compiled with -Xlint:unchecked
)
This makes the warnings go away...
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;
}