0

I'm trying to access a HashMap<String, Number> via reflection:

Serializable obj; //here goes the HashMap
String name;
...
return (double)obj.getClass().getDeclaredMethod("get", Object.class).invoke(obj, name);

but so far all I got is a casting error caused by the line above:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double

Indeed, the map value that was accessed by the key name was Integer.So I've changed the line to:

return obj.getClass().getDeclaredMethod("get", Number.class).invoke(obj, name).doubleValue();

but that didn't work out either. I even got doubleValue() underlined as "undefined for the type Object" (but why Object if I have Number.class?).

I'm not sure what casting rules I'm breaking. Can someone, please, help me if my map entries have various number values (Integer, Float, Double) but I need the method to return a double value (primitive).

PS It's not really a duplicate. My question is more general. But thank you for your input. I forgot that invoke always returns Object.

The working code is:

return ((Number)obj.getClass().getDeclaredMethod("get", Object.class).invoke(obj, name)).doubleValue();
Vic
  • 211
  • 2
  • 9
  • What's the return type of `invoke`? Does that type have a `doubleValue` method? – Sotirios Delimanolis Jun 01 '16 at 19:26
  • 1
    You could just cast to `Integer` and the compiler will take care of making the conversion to `double` for you. Otherwise, cast to `Integer` or `Number` and then invoke its `doubleValue` method. – Sotirios Delimanolis Jun 01 '16 at 19:29
  • @Sotirios Delimanolis The return type should be Number since I try to get the Number element from a HashMap. So it should have a doubleValue method. The actual value of the HashMap element in this case is Integer as I wrote before. – Vic Jun 01 '16 at 19:34
  • Please pull up the javadoc of the method and verify. (It's `Object`, not `Number`). – Sotirios Delimanolis Jun 01 '16 at 19:35

1 Answers1

0

You have the wrong assumption that there was a relationship between the Class object you pass to getDeclaredMethod and the return type of Method.invoke. The Class objects you pass to getMethod or getDeclaredMethod describe the parameter types of the method, not the return type. Further, while you may pass arguments of a subtype of the declared parameter type, you have to specify exactly the declared parameter type.

The parameter type of Map.get is Object, invariably, so you have to specify Object.class, regardless of which actual key you will pass to invoke.

Further, due to type erasure, the reflective return type will always be Object, regardless of the actual Map’s parametrization. Not that it matters, Method.invoke’s declared return type is always Object, as different Method instances may represent different methods.

So when you have a HashMap<String, Number>, you can rely on the returned objects to be Number instances, but not necessarily Double instances. As you experienced, there could be Integer instances. So what you have to do, is to type-cast to Number, followed by invoking doubleValue():

return ((Number)obj.getClass().getDeclaredMethod("get", Object.class)
        .invoke(obj, name)).doubleValue();

That said, if you know that the object implements the Map interface, there is no reason to use Reflection at all:

return ((Number)((Map<?,?>)obj).get(name)).doubleValue();

does the job much more efficient.

Holger
  • 285,553
  • 42
  • 434
  • 765