Casting from Object in Java without getting an unchecked warning

26,867

Solution 1

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?

Solution 2

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.

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
26,867
swampsjohn
Author by

swampsjohn

Updated on March 21, 2020

Comments

  • swampsjohn
    swampsjohn about 4 years

    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?

  • swampsjohn
    swampsjohn over 14 years
    Only some are Widgets. "bar" was just an example, any key could have a Widget
  • Alexander Pogrebnyak
    Alexander Pogrebnyak over 14 years
    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.
  • ChssPly76
    ChssPly76 over 14 years
    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.
  • swampsjohn
    swampsjohn over 14 years
    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
  • ChssPly76
    ChssPly76 over 14 years
    Now that would actually trigger an unchecked cast warning.
  • swampsjohn
    swampsjohn over 14 years
    Yeah, I'm doing something vaguely similar to ResultSet
  • BalusC
    BalusC over 14 years
    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 ;)
  • ChssPly76
    ChssPly76 over 14 years
    @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
    ChssPly76 over 14 years
    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.
  • Thomas Jung
    Thomas Jung over 14 years
    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
    Thomas Jung over 14 years
    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.