0

I have the following code:

String test = "[{\"color\":\"red\"}]";
Class<? extends Base> baseObject = Base.class;
Collection<? extends Base> elements = new ArrayList<Base>();
 if (test.startsWith("[")) {
    elements.addAll(new ObjectMapper().readValue(test, Collection.class));
 } else {
   elements.add(new ObjectMapper().readValue(test, baseObject));
}

However I get on

elements.addAll(new ObjectMapper().readValue(test, Collection.class));

a compilation warning:

The expression of type Collection needs unchecked conversion to conform to Collection<? extends capture#1-of ? extends Base>

and for elements.add(new ObjectMapper().readValue(test, baseObject));

a compilation error:

The method add(capture#2-of ? extends Base) in the type Collection<capture#2-of ? extends Base> is not applicable for the arguments (capture#3-of ? extends Base)

What is wrong?

Dejell
  • 13,947
  • 40
  • 146
  • 229
  • 1
    Why not simply `Collection elements = new ArrayList();`? – assylias Jun 11 '13 at 14:56
  • 2
    `String test = "[{"color":"red}]";` <-- i think you forgot to escape the quotes – Marco Forberg Jun 11 '13 at 14:57
  • Did you paste that code directly? Because `String test = "[{"color":"red}]";` doesn't compile because you didn't escape the quotes: `String test = "[{\"color\":\"red}]";` – Brian Jun 11 '13 at 14:57
  • @Brian - sure - sorry about it - ONly the string I wrote by hand. I will fix it – Dejell Jun 11 '13 at 14:58
  • @assylias Since it could be an element that extends Base. If I change it to Base - I don't get the compilation error, but I still get the warning – Dejell Jun 11 '13 at 14:59
  • 1
    See my answer [here](http://stackoverflow.com/questions/12604477/use-of-extends-and-super-in-collection-generics/12605337#12605337) – Brian Jun 11 '13 at 14:59
  • @Odelya Which would be fine. You can put an `Integer` in a `List` if you want to. – assylias Jun 11 '13 at 15:00
  • @assylias Thanks - what about the warning? The compilation error has been removed – Dejell Jun 11 '13 at 15:03
  • @Brian so what would be my solution to this case? – Dejell Jun 11 '13 at 15:12

2 Answers2

2

Error can be easily explained. Your collection is defined to hold instances of classes that extends Base. ObjectMapper.readValue is defined as following:

public <T> T readValue(JsonParser jp, Class<T> valueType)

This means that it returns instance of class specified as a second argument. If second argument is Collection.class this method returns Collection. Just Collection, not Collection<? extends Base> and not Collection<Base>. So, java compiler cannot be sure that you are going to put collection that contains correct objects into elements defined as Collection<? extends Base>. Moreover java syntax does not allow to supply as a parameter class with generics, i.e. you cannot call readValue(c, Collection<Base>.class).

The second case is more complicated. baseObject is defined as Class<? extends Base>. elements collection is defined as Class<? extends Base> too. So, what's the problem. The problem is in ?. The question mark means "something that extends Base". But these are 2 different "something" in both cases.

The solution can be either change definition of collection to Class<Base> or to use one class or method generic parameter in both cases, e.g.:

public <T extends Base> myMethod() {
    Class<T> baseObject = Base.class;
    Collection<T> elements = new ArrayList<T>();
    elements.add(new ObjectMapper().readValue(test, baseObject));
}

Now both baseObject and elements use the same type T that indeed extends Base.

AlexR
  • 114,158
  • 16
  • 130
  • 208
1

You can't add things to a collection with a wildcard type (that doesn't have an explicit lower bound using super, according to Brian; that does make some sense, as a subclass can always be cast to one of its superclasses, but I haven't used super-bounded wildcards much before so I'm not too sure of the details).

http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html

Specifically:

Since we don't know what the element type of c stands for, we cannot add objects to it. The add() method takes arguments of type E, the element type of the collection. When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don't know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.

Type erasure is a... well, you know.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • 1
    Not quite. You can add things to a collection that uses `super`, e.g. you can add any subclass of `InputStream` to a `Collection super InputStream>` – Brian Jun 11 '13 at 15:02
  • I still get a compilation warning for elements.addAll(new ObjectMapper().readValue(jsonRep, Collection.class)); – Dejell Jun 11 '13 at 15:09
  • @Odelya Conversions between generic types and their raw versions always yield warnings about unchecked conversions. It's more of a message from the compiler that what you're doing could have issues, but it can't know until runtime if there actually will be any. (Or something like that.) You can ignore the warning with `@SuppressWarnings("unchecked")`. – JAB Jun 11 '13 at 15:17
  • I added the suppresswarning - but isn't a better way to do it? otherwise - does the compiler add it? – Dejell Jun 11 '13 at 15:27
  • @Odelya If my guess that you're parsing JSON (going by your test string) is correct, a better way to do things would be to use an existing parser. http://eclipsesource.com/blogs/2013/04/18/minimal-json-parser-for-java/ – JAB Jun 11 '13 at 15:31