views:

450

answers:

6

I wrote a class that has a map of <String, Object>. I need it to hold arbitrary objects, but at the same time sometimes I need to cast some of those objects, so I'll do something like

HashMap<String, Object> map = new HashMap<String, Object>();                                                                                 
Object foo = map.get("bar");                                                                                                                                                                                                         
if (foo instanceof HashMap) {                                                                                                                                                                                                        
    ((HashMap<String, Integer>) foo).put("a", 5);                                                                                                                                                                                    
}

which gives the warning

Stuff.java:10: warning: [unchecked] unchecked cast
found   : java.lang.Object
required: java.util.HashMap<java.lang.String,java.lang.Integer>
        ((HashMap<String, Integer>) foo).put("a", 5);

I suspect it has to do with the use of generics. I can get rid of the error using @SupressWarnings("unchecked"), but I was wondering if there was a better way to do it. Or maybe the fact that I'm getting the warning means I should reconsider what I'm doing. Is there anything I could do, or should I just use @SupressWarnings?

+1  A: 

Or maybe the fact that I'm getting the warning means I should reconsider what I'm doing.

You got the point. Logical step would be to create a Map<String, Widget> instead of a Map<String, Object>. If that's not an option for some reason, you could do something like:

Widget w = Widget.class.cast(foo);
w.spin();

This doesn't give a compiler warning anymore, but this still doesn't necessarily mean that your Map of mixed objects is a good practice.

Edit: as ChssPly76 pointed out, this should actually not have generated an "unchecked cast" warning. I tested it in Eclipse and it indeed didn't gave the particular warning. Can you post an SSCCE (a class with a main() purely demonstrating the problem) so that we can better understand what's going on?

Edit 2: so you're using a map which may contain generic structures such as maps. That explains the bit. Well, aside from redesigning the structure, I don't see any other option than just live with the @SuppressWarnings("unchecked") annotation.

BalusC
+1  A: 

If your Map is holding objects of the same type (e.g. all Widgets), then you can use Map<String,Widget> to eliminate both the cast and the warning.

If you're holding objects of arbitrary type, however, then this shows you have a deeper design problem. If you know what type the object will be based on the name (e.g. "bar" always gets you a Widget) then consider using an object with a method called Widget getBar() rather than a Map.

If you don't know what "bar" will be when you get it from the map, you have an even deeper design problem and should consider using some Object Oriented principles to reduce coupling.

Ross
Only some are Widgets. "bar" was just an example, any key could have a Widget
swampsjohn
I strongly disagree with your notion that using polymorphic map values necessitates the existence of "an even deeper design problem". There are many scenarios were that might be necessary - things like EAV / dynabean / ResultSet probably being the most common. Granted, things don't always have to be exposed via API in this way, but that's what they are behind the scenes - your basic Map.
ChssPly76
Yeah, I'm doing something vaguely similar to ResultSet
swampsjohn
+2  A: 

Edited (based on question clarification)

Casting to HashMap<String, Integer> (btw, using Map instead of HashMap is arguably a better choice) is a different story. There's sadly no way to avoid an unchecked warning in that case due to type erasure. You can, however, use it as non-generic map:

if (foo instanceof Map) {                                                                                                                                                                                                        
  ((Map) foo).put("a", 5);                                                                                                                                                                                    
}

You'll obviously have to cast on "gets" and you lose (perceived) type safety but there'll be no unchecked warning.


There must be more to this story. The following code:

Map<String, Object> map = Maps.newHashMap(); // or new HashMap<String, Object>();
Object foo = map.get("bar");
if (foo instanceof Widget) {
  ((Widget) foo).spin();
}

does NOT generate an unchecked warning for me. Nor can I imagine why would it. If you know beforehand that "bar" would always return a widget, doing this:

Widget widget = (Widget) map.get("bar");
widget.spin();

would work perfectly fine as well. Am I missing something here?

ChssPly76
Was typing something along these lines. I suspect OP is passing or assigning to a raw `Map` , then, of course, the unchecked warning is generated.
Alexander Pogrebnyak
Huh, you're quite right. I'm sorry, I didn't actually test the snippet. After some more testing, I think the problem only occurs when trying to cast to a generic. I'll posted a new snippet that does trigger the warning
swampsjohn
As to your edit: the unparameterized Map would give a new warning about raw types that needs to be parameterized. Not sure which one is more annoying in the eyes of the beholder ;)
BalusC
@BalusC - it shouldn't. You're not creating a new map instance, you're casting. It works for me in Eclipse / java 1.5
ChssPly76
A: 

Not sure how you're using the objects, but there's:

for(Map.Entry<String, Widget> entry = map.entrySet())
{
     entry.getValue().spin();
}
Droo
Now **that** would actually trigger an unchecked cast warning.
ChssPly76
A: 

i think the underlying problem is the Object class

HashMap<String, Object> map;

if you want to remove the casting warning, then you need to specify a base class / interface.

for example you could do do this

Map<String, Animal> map = new LinkedHashMap<String, Animal>(); 
Animal pet = map.get("pet"); 
pet.feed();

instead of

Map<String, Object> map = new LinkedHashMap<String, Object>();
Object pet = map.get("pet");
if (pet instance of Dog)
{
        ((Dog)pet).feedDog();
}
if (pet instance of Cat)
{
        ((Cat)pet).feedCat();
}

the main use of a map is to put similar things together.

if you want to really put different things then consider writing a new class.

Titi Wangsa bin Damhore
+1  A: 

If everything else (polymorphic implementation, casts) is not applicable you can implement a heterogeneous container (slide 32). This is described in Item 29: Considertype-safe heterogeneous containers in Effective Java 2 nd Edition. The responsibility of the container is to ensure type-safeness.

public class Container{
  private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
  public <T> void set(Class<T> klass, T thing) {
    favorites.put(klass, thing);
  }
  public <T> T get(Class<T> klass) {
    return klass.cast(favorites.get(klass));
  }
}

The problem with your example is that you're using a HashMap<K,V> as an entry type. This cannot be represented with a class literal as a type token. So you have to implement some form of super type token:

public abstract class TypeReference<T> {}

Your client code would then extend TypeReference for every type token needed:

TypeReference<?> typeToken = new TypeReference<HashMap<String, Integer>>{};

The type information is accessible at run-time. The container implementation has then to type check against the actual type parameters of of the type token (subclass of TypeReference).

This is a complete solution but a lot of work to implement. No collection library I know of does support containers with type references.

Thomas Jung
This is an interesting approach (+1), but I see two problems with it: (1) it requires me to know beforehand the type of value I'm going to ask for which is not always applicable and (2) at a certain level (somewhere about 2-3, your tolerance may vary) this starts to look **a lot** uglier than straight-up cast or `@SuppressWarnings`. (1) can technically be worked around since type info is retained but that again gets into ugliness of dealing with ParameterizedType and what not.
ChssPly76
Well, this is java. Why can't we have type/method/field literals that are useful but a one shot class literal? Thanks a lot Sun.
Thomas Jung
Type references are ugly. But even Sun has to use them. I've forgotten the exact place but somewhere in JEE 6 it is used to get some annotation type. This was quite ugly.
Thomas Jung