1

I've been writing a MongoDB interface in java and recently ran into this code snag.

I have a call to insert a document:

insertMongoDocument(String testName, Map<String, Object> input)

And I have the corresponding unit test:

Writer.insertMongoDocument("TestName", testMap);

Test map is of the form

protected static Map<String, List<?>> testMap;

Why is this an illegal argument? Is a Map<String, List<?>> not a Map<String, Object>?

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • 2
    Covariance of generic types is a nonexistent feature in Java. Yes, those are different types for Java. – allprog Sep 25 '13 at 14:27

3 Answers3

4

In java, the type has to match exactly as declared. That is given

insertMongoDocument(String testName, Map<String, Object> input)

you can't call it with

Map<String, List<?>> map;
insertMongoDocument("foo", map);

Even though it seems that Map<String, List<?>> should be an instance of Map<String, Object>, it isn't.

This is another case of generics seeming to not follow basic inheritance, ie List<SubClass> is not an instance of List<SuperClass>.

If it were true, it would lead to this bug:

List<Integer> listI = new ArrayList<Integer>();
List<Number> listN = listI; // compile error, but let's assume OK
listN.add(1.2); // Adding a Double (which is a Number) to a list of Integer - oops!
Integer i = listI.get(0); // BOOM... ClassCastException!

The line

List<Number> listN = listI;

is not allowed for what now should be obvious reasons, and this is basically what you're seeing: You can't cast a generic type like you would a class.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
2

You need to use the following signature

insertMongoDocument(String testName, Map<String, ? extends Object> input)

The reason for this is that Java cannot relate parametric types. It tries to apply only the rules that it knows about. In your original signature it knows that it can accept Object for the second type parameter of Map, then it finds List<?> which is not an exact match so the error is returned. The extend and super operators need to be used to specify a set of applicable types instead of an exact match.

The one and only Jon Skeet has an excellent explanation on this topic: What is a difference between <? super E> and <? extends E>?

Community
  • 1
  • 1
allprog
  • 16,540
  • 9
  • 56
  • 97
  • This is exactly what I ended up changing it to. How is this type safe though since everything extends object? (Not that it's relevant, but an interesting thought) – Christopher Lates Sep 25 '13 at 14:37
  • Yes, but in the body of the method you can be sure that the objects returned from the Map comply at least with the Object interface. This is a very important detail. – allprog Sep 25 '13 at 14:39
0

Because if it were legal, you could pass it to a method like insertMongoDocument which could attempt to call input.put("", new Anything()) which would destroy type safety.

The most general type you can use for a signature here is insertMongoDocument( String s, Map< String, ? super List< ? > > ).

Judge Mental
  • 5,209
  • 17
  • 22