开发者

Casting from Object in Java without getting an unchecked warning

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 wonder开发者_如何学Going 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?


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?


If everything else (polymorphic implementation, casts) is not applicable you can implement a heterogeneous container as described in Item 33: Consider type-safe heterogeneous containers in "Effective Java", 3rd 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.


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.


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.


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.


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

for(Map.Entry<String, Widget> entry = map.entrySet())
{
     entry.getValue().spin();
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜