3

I have the following Java code:

List<? extends Collection<String>> attributes = new ArrayList<HashSet<String>>(nrAttributes);

...

attributes.replaceAll((in) -> {
        List<String> out = new ArrayList<>(); 
        out.addAll(in); 
        return out;
    });

But it gives me a compiler exception (last out is red) saying:

Type mismatch: cannot convert from List to capture#2-of ? extends Collection

user2908112
  • 427
  • 4
  • 16
  • Compiles fine for me, which jdk version ? – Jean-François Savard Jun 03 '16 at 11:45
  • jdk 1.8 so it should work? – user2908112 Jun 03 '16 at 11:51
  • 1
    No, it should not work. And the compiler is doing a fine job here. Consider what you are doing: With `new ArrayList>(...)` you created a _list of sets_. But now you want to replace those sets with lists, thus making it a _list of lists_ which contradicts the object you created. – Seelenvirtuose Jun 03 '16 at 11:53
  • 1
    @Seelenvirtuose Who care what was originally created ? The attributes variable is suppose to hold any Collection of Strings. This is the only thing that will be validated by the compiler. You can try to compile it. It will also run fine. – Jean-François Savard Jun 03 '16 at 11:56
  • 1
    @Jean-FrançoisSavard "The attributes variable is suppose to hold any Collection of Strings" => Unfortunately this is wrong. The variable `attributes` is supposed to hold a `List` (or a subtype) whose elements are all (!) of a concrete (but unknown) subtype of `Collection`. As the compiler can't know which subtype actually was chosen (here in fact it was `HashSet`), it can't allow to put another type into the list - here a `List`. – Seelenvirtuose Jun 03 '16 at 11:58
  • @Seelenvirtuose Please re-read *"is suppose to hold"*, seems to me that you read it like a simple *"is"*. "is" is the list, which contains collections of strings. In that case, the `replaceAll` method will expect `The `replaceAll` in that case will expect as argument `UnaryOperator extends Collection>` as argument which is perfectly fine here. And this code does compile with Java 8. – Jean-François Savard Jun 03 '16 at 12:01
  • It does not compile for me JDK1.8_91 – Jorn Vernee Jun 03 '16 at 12:03
  • @JornVernee Any chances you are using eclipse mars ? – Jean-François Savard Jun 03 '16 at 12:03
  • 1
    @Jean-FrançoisSavard "which contains collections of strings" => NO! The list that his variable refers to does not contain any object of type `Collection`. It does contain objects whose type is a concrete but unknown subtype of `Collection`. This subtype is fixed when instantiating the list. – Seelenvirtuose Jun 03 '16 at 12:04
  • @Jean-FrançoisSavard Using command line – Jorn Vernee Jun 03 '16 at 12:06
  • @Seelenvirtuose You are playing on words. Aren't all subtype of a type the subtype itself ? – Jean-François Savard Jun 03 '16 at 12:08
  • 1
    Although this is a correct statement, it doesn't hold true for generics. Consider a variable `List extends Number> list = new ArrayList()`. This does not mean that all list elements are of _any_ subtype of `Number`. It means that the elements' type is fixed while creating the list object to a concrete but unkown type. In fact, you make the variable to be of type `List`. So you can't put any other `Number` (such as an `Integer`) into it. That's it. It seems that I can't find the right words for explaining these very complicated rules. JLS is also of no help, but worth a read. – Seelenvirtuose Jun 03 '16 at 12:29

4 Answers4

3

Your extends just ensures you that you will read a Collection<String> from your list. But you can't write it.

For a very good explanation see this link where i took the following from:

You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

Community
  • 1
  • 1
Felix S.
  • 116
  • 4
2

Command line says:

J:\WS\Java test>javac Main.java -Xdiags:verbose
Main.java:11: error: method replaceAll in interface List<E> cannot be applied to given types;
        attributes.replaceAll((in) -> {
                             ^
  required: UnaryOperator<CAP#1>
  found: (in)->{ Li[...]ut; }
  reason: argument mismatch; bad return type in lambda expression
      List<String> cannot be converted to CAP#1
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Collection<String> from capture of ? extends Collection<String>
1 error

i.e. the ? capture, might be a sub-type or sibling of List<String>, so it is unsafe to add a List<String> to a List<? extends Collection>. The ...extends Collection doesn't really matter here.


Possible solution:

List<? extends Collection<String>> attributes = new ArrayList<HashSet<String>>(nrAttributes);

...

List<List<String>> attribs2 = new ArrayList<>();

attributes.forEach((in) -> {
    List<String> out = new ArrayList<>();
    out.addAll(in);
    attribs2.add(out);
});

Or:

List<Collection<String>> attributes = new ArrayList<>(nrAttributes);        

...

attributes.replaceAll((in) -> {
    List<String> out = new ArrayList<>();
    out.addAll(in);
    return out;
});
Jorn Vernee
  • 26,917
  • 3
  • 67
  • 80
  • Yeah, I think so. The decision about the type is apparently made at compile-time and can't be changed either – user2908112 Jun 03 '16 at 12:29
  • The `extends` keyword is specifically why you cannot modify the collection. So it really matters here. – Wis Jun 03 '16 at 12:32
1

You defined your attributes variable as a list of something that extends a collection of strings. At compile-time, the compiler cannot decide if attributes is in fact an instance of List<TreeSet<String>, or a List<LinkedList<String> or even a List<Vector<String>. Consequently, the compiler does not allow you to modify the attributes collection because there is a risk that you are adding an ArrayList<String> to a collection of another collection of strings (say TreeSet<String>.

In general, if you have a List<? extends Something>, the compiler wont allow you to modify the collection (add/remove/clear/...). Any list typed with ? extends T is read-only !

Compiling :

List<Collection<String>> attributes = new ArrayList<>(nrAttributes);

Wis
  • 613
  • 1
  • 10
  • 31
-1

When you say List> you are saying "A List of a particular subtype of Collection" All such ? extends X are named internally as capture#n of ? extends X (the first time it's capture#1..., the second capture#2... and so on), but every time it's considered a particular type. For instance, the following code does not compile either, with the same error

Why do you need the generics for? the following example can contain any subclass of Collection of String (compiles, btw)

    List<? extends Collection<String>> attributes = new ArrayList<HashSet<String>>(nrAttributes);

    attributes.replaceAll((in) -> {
        List<String> out = new ArrayList<>();
        out.addAll(in);
        return out;
    });
dtortola
  • 718
  • 4
  • 6