views:

13240

answers:

15

Eclipse is giving me a warning of the following form:

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

This is from a call to an API that I have no control over which returns Object:

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

I'd like to avoid Eclipse warnings, if possible, since theoretically they indicate at least a potential code problem. I haven't found a good way to eliminate this one yet, though. I can extract the single line involved out to a method by itself and add @SuppressWarnings("unchecked") to that method, thus limiting the impact of having a block of code where I ignore warnings. Any better options? I don't want to turn these warnings off in Eclipse.

Before I came to the code, it was simpler, but still provoked warnings:

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

Problem was elsewhere when you tried to use the hash you'd get warnings:

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.
+3  A: 

A quick guess if you post your code can say for sure but you might have done something along the lines of

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

which will produce the warning when you need to do

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

it might be worth looking at

Generics in the Java Programming Language

if your unfamiliar with what needs to be done.

Mark Davidson
Unfortunately it's not that easy a situation. Code added.
skiphoppy
I came here looking for an answer to a slightly different problem: and you told me exactly what I needed! Thanks!
staticsan
+1  A: 

I may have misunderstood the question(an example and a couple of surrounding lines would be nice), but why don't you always use an appropriate interface (and Java5+)? I see no reason why you would ever want to cast to a HashMap instead of a Map<KeyType,ValueType>. In fact, I can't imagine any reason to set the type of a variable to HashMap instead of Map.

And why is the source an Object? Is it a parameter type of a legacy collection? If so, use generics and specify the type you want.

phihag
I'm pretty sure that switching to Map in this case would not change anything, but thanks for the programming tip, which may change the way I do some things, for the better. The source of the object is an API I have no control over (code added).
skiphoppy
+15  A: 

The obvious answer, of course, is not to do the unchecked cast.

If it's absolutely necessary, then at least try to limit the scope of the @SuppressWarnings annotation. According to its Javadocs, it can go on local variables; this way, it doesn't even affect the entire method.

Example:

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

There is no way to determine whether the Map really should have the generic parameters <String, String>. You must know beforehand what the parameters should be (or you'll find out when you get a ClassCastException). This is why the code generates a warning, because the compiler can't possibly know whether is safe.

Michael Myers
+1 for pointing out that it can go on local variables. Eclipse only offers to add it to the whole method...
thSoft
A: 

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.

Rick
Unfortunately, generics render that impossible. It's not just a HashMap, it's a HashMap with type information. And if I eliminate that information, I'll just push the warnings to elsewhere.
skiphoppy
SkipHoppy is right. I was just about make the same comment.
Julien Chastang
+1  A: 

The problem lies in here:

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

The result of session.getAttribute(...) is an object which could be anything, but since you "know" it's a HashMap<String, String> you're just casting without checking it first. Thus, the warning. To be pedantic, which Java wants you to be in this case, you should retrieve the result and verify it's compatibility with instanceof.

JMD
Unfortunately, it seems that instanceof can't handle the parameterization, which is what causes the problem in the first place.
skiphoppy
Right, and sorry for the red herring, because even if you remove the parameterized type from the HashMap and verify its compatibility with instanceof the cast is *still* going to give you the same warning, regardless.
JMD
Whoa, same question, essentially the same answer, and he even used the word "pedantic" with regards to Java!http://stackoverflow.com/questions/262367/type-safety-unchecked-cast
JMD
+4  A: 

Wow; I think I figured out the answer to my own question. I'm just not sure it's worth it! :)

The problem is the cast isn't checked. So, you have to check it yourself. You can't just check a parameterized type with instanceof, because the parameterized type information is unavailable at runtime, having been erased at compile time.

But, you can perform a check on each and every item in the hash, with instanceof, and in doing so, you can construct a new hash that is type-safe. And you won't provoke any warnings.

Thanks to mmyers and Esko Luontola, I've parameterized the code I originally wrote here, so it can be wrapped up in a utility class somewhere and used for any parameterized HashMap. If you want to understand it better and aren't very familiar with generics, I encourage viewing the edit history of this answer.

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

That's a lot of work, possibly for very little reward... I'm not sure if I'll use it or not. I'd appreciate any comments as to whether people think it's worth it or not. Also, I'd appreciate improvement suggestions: is there something better I can do besides throw AssertionErrors? Is there something better I could throw? Should I make it a checked Exception?

skiphoppy
You can't parameterize it unless you add two Class parameters also. Your signature would be "public static <K,V> HashMap<K,V> checkHash(HashMap<?,?> input, Class<K> keyClass, Class<V> valueClass)". Then you'd use keyClass.isAssignableFrom(key.getClass()) and keyClass.cast(key).
Michael Myers
I'm not sure it's worth it; it depends on the context. An alternative would be to declare the method like this: Map<?,?> getItems() and do the cast to String in the client code. Properly use of generics assures that a ClassCastException is never raised in the absence of an explicit cast.
erickson
+4  A: 
Julien Chastang
+1  A: 

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.

Fortyrunner
A: 

You can create a utility class like the following, and use it to suppress the unchecked warning.

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

You can use it as follows:

import static Objects.uncheckedCast;
...

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

Some more discussion about this is here: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

Esko Luontola
Don't know why this was voted down, it works just fine.
Software Monkey
+2  A: 

In this particular case, I would not store Maps into the HttpSession directly, but instead an instance of my own class, which in turn contains a Map (an implementation detail of the class). Then you can be sure that the elements in the map are of the right type.

But if you anyways want to check that the contents of the Map are of right type, you could use a code like this:

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 Luontola
Awesome; I think I can combine that with my answer to parameterize it and avoid having to suppress warnings altogether!
skiphoppy
A: 

Almost every problem in Computer Science can be solved by adding a level of indirection*, or something.

So introduce a non-generic object that is of a higher-level that a Map. With no context it isn't going to look very convincing, but anyway:

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

*Except too many levels of indirection.

Tom Hawtin - tackline
+1  A: 

In the HTTP Session world you can't really avoid the cast, since the API is written that way (takes and returns only Object).

With a little bit of work you can easily avoid the unchecked cast, 'though. This means that it will turn into a traditional cast giving a ClassCastException right there in the event of an error). An unchecked exception could turn into a CCE at any point later on instead of the point of the cast (that's the reason why it's a separate warning).

Replace the HashMap with a dedicated class:

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

Then cast to that class instead of Map<String,String> and everything will be checked at the exact place where you write your code. No unexpected ClassCastExceptions later on.

Joachim Sauer
A: 

If you are sure that the type returned by session.getAttribute() is HashMap then you can not typecast to that exact type, but rely on only checking the generic HashMap

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

Eclipse will then surprise warnings, but of course this can lead to runtime errors that can be hard to debug. I use this approach in not operation-critical contexts only.

Lukas Normantas
+1  A: 

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;
}
A: 

Take this one, it's much faster than creating a new HashMap, if it's already one, but still secure, as each element is checked against it's type...

@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);
    }
jfreundo