0

Requirement: method1 should accept object of any type such as Map<String,User>, Map<String,Employee>, Map<String,Account> etc.

Scenario1:

method1(Map<String,Object> map)
{
method2(map); //signature -> method2(Map<String,Object> map){//code here}
}

Problem: method1 is accepting only argument of type Map<String,Object>. However, there is no problem in passing argument from method1 to method2

Scenario2:

method1(Map<String,? extends Object> map)
{
method2(map); //signature -> method2(Map<String,Object> map){//code here}
}

Now, method1 is accepting argument of any type Map<String,Object>, Map<String, User>. However, there is a problem in passing argument from method1 to method2

Problem: compile time exception when passing argument from method1 to method2. exception: required type Map<String, Object>, provided: Map<String, capture of ? extends Object>

I have control over method1, I can change the signature. But, it needs to meet above requirement. I don't have control over method2. I must pass argument of type Map<String, Object>.

Solution: `method2((Map<String, Object>) map)

Earlier, there was a mistake in typecasting. Now code is working fine. Thanks for responses.

  • What is the exception? – Roddy of the Frozen Peas Apr 19 '21 at 21:45
  • Required type Map, provided Map – user13944893 Apr 19 '21 at 21:51
  • Cast to Map, but you'll get a warning, since you are doing an implicitly unsafe operation. Consider the case where the argument of method1 is a Map, maybe method2 is adding some not String object to the map. The result is a Map of string values containing other objects. – Rocco Apr 19 '21 at 21:52
  • Does this answer your question? [Why a MAP use generic wildcard extends Object cannot hold any type?](https://stackoverflow.com/questions/63106039/why-a-map-use-generic-wildcard-extends-object-cannot-hold-any-type) – Alberto Sinigaglia Apr 19 '21 at 21:54
  • Further reading material: https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super – Silvio Mayolo Apr 19 '21 at 22:01
  • Thanks for the response. I want method1 to accept object of any class type such as User, Employee, Account etc. I tried to typecast, but getting compile time exception. Code - map.values().forEach(value -> (Object) value). exception - Bad return type in lambda expression: Object cannot be converted to void. Please help if there is any mistake in code. – user13944893 Apr 19 '21 at 22:02

1 Answers1

2

You cannot safely do so, because Map is a mutable type.

Suppose we could safely do so. That is, suppose we had a map called foo whose type was, say, Map<String, File>. This is a perfectly valid instance of Map<String, ? extends Object> as File does indeed extend Object. So it's a valid argument to your method. Now, we cast foo to Map<String, Object> and then call foo.put("example key", 100) to add an Integer to our map. Integers are valid objects, so this typechecks. Now we have a map foo whose compile-time type is Map<String, File> but which contains an integer somehow. What happens later if I get a file out of this map and try to read from it or write to it? If it's actually an integer, that would crash the JVM.

Specifically, Java has call-site variance annotations. When you say you're taking Map<String, ? extends Object>, you're declaring that the second argument is covariant for the moment, which among other things means you can only read, not write, values. If you had said Map<String, ? super Number>, then it would be contravariant, which means you can only write. Since the function you're trying to call declared the type as Map<String, Object>, that function's contract makes no guarantees about whether or not it's reading or writing, and the type is invariant.

If you're absolutely sure that the function you're calling doesn't modify the map and only reads it, then you can cast explicitly to get the type you want (after all, generics have no runtime representation). But if that's really the case, then the person who wrote that function made a mistake. If you have control over it, you might consider correcting the type signature of the function to take a Map<String, ? extends Object>, as it only generalizes the type and allows strictly more possible calls, not fewer.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • Thanks for the response. Intially method1 input argument type is Map. It's throwing error when Map map is passed. So, I changed it to Map. Now, method1 is accepting all class types. Now, the problem is with method2.I don't have control over method2 signature. It accepts only Map map. So, I need to typecast the input argument of method1. – user13944893 Apr 19 '21 at 22:18