10

Given:

import java.util.*;

public class Hancock {
    //insert code here
        list.add("foo");
    }
}

Which two code fragments, inserted independently at line 5, will compile without warnings? (Choose two)

A. public void addString(List list) {
B. public void addString(List<String> list) {
C. public void addString(List<? super String> list) {
D. public void addString(List<? extends String> list) {

Correct answers are B & C.

Answers A and B are quite clear for me. For the answers C & D i know which way the inheritence is going, however i cannot understand why answer D does not compile in Eclipse while all others do (A with warrning about generic, B & C without warrings).

Error in Eclipse for answer D is The method add(capture#1-of ? extends String) in the type List<capture#1-of ? extends String> is not applicable for the arguments (String).

On the other hand this compiles:

public void addString() {
    List<? extends String> list1 = new ArrayList<String>();
    List<? super String> list2 = new ArrayList<String>();
}

Why? Why <? super String> does not compile in method declaration while it does compile in variable declaration.

I know that String is final class and cannot be extended by any other class but that does not explain to me what is going on here.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Dáve
  • 161
  • 1
  • 12
  • 1
    possible duplicate of [difference between super T> and extends T> in Java](http://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java) – Damian Leszczyński - Vash Jul 21 '14 at 09:34
  • possible duplicate of [Java Generics: What is PECS?](http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs) – Harry Blargle Jul 21 '14 at 10:26

2 Answers2

9

First, let's see answer C:

public void addString(List<? super String> list) {
    list.add("foo");
}

This method declaration says that you will be allowed to pass List objects which are parametrized by some super class of String, for example String or Object. So:

  1. If you pass List<String> the list.add("foo") will be perfectly valid.
  2. If you pass List<Object> the list.add("foo") will be perfectly valid, because "foo" is a String (and you can add a String to a List<Object>).

This means that answer C is correct.


Lets now see answer D.

If you have a method declaration like this:

public void addString(List<? extends String> list) {

}

this means that you will be able to pass List objects parametrized by some unknown subtype of String. So, when you do list.add("foo"); the compiler won't be aware if the provided object has a type that matches the unknown subtype of String and therefore raises a compile-time error.


When you have:

public void addString() {
    List<? extends String> list1 = new ArrayList<String>();
    List<? super String> list2 = new ArrayList<String>();
}

This fragment compiles fine, because list1 is defined to hold List objects that are of some unknown subtype of String, including the String itself, which is why it's valid. The problem is that you won't be able to add anything, except null.

As for list2, the variable can hold List objects which are parametrized by some super-type of String, including the String itself.


More info:

Community
  • 1
  • 1
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • Actually your explanation of "C" is wrong. You *cannot* add an `Object` to a `list super String>`. The list *could* be a `List` during runtime, so this will fail. – dognose Jul 21 '14 at 09:44
  • 1
    I disagree. Try `List super String> list = new ArrayList();` and pass it to the `addString` method. – Konstantin Yovkov Jul 21 '14 at 09:47
  • your example adds a `string` (which is the only valid thing you can add). If you try to add an object, you'll get: `no suitable method found for add(Object) myList.add(new Object());` – dognose Jul 21 '14 at 09:49
  • 2
    Read carefylly what I've written and tell me where do I say that you can add `Object` instances? I've said that you can pass `List`, not that you can add `Object`s :) – Konstantin Yovkov Jul 21 '14 at 09:51
  • touche. How did you remove that comment about "add" - even from the revision history :P – dognose Jul 21 '14 at 09:56
  • I think that PECS is the explanation i was looking for. – Dáve Jul 21 '14 at 10:19
2

Firstly, generics don't care that String is final. They work the same way for final and non-final classes.

With that in mind, it should be apparent why D is not allowed - if it was, you could do this:

void test() {
    List<Integer> integers = new ArrayList<Integer>();
    addADouble(integers);
    int a = integers.get(0); // ????
}

void addADouble(List<? extends Number> list) {
    list.add(new Double(5.0));
}

List<? extends Number> is a "List of something that extends Number, but you don't know exactly what it's a List of." - it might be a List<Double>, or a List<Integer>, or a List<Number>, or a List<YourCustomSubclassOfNumber>, so you can't add anything to it because you don't know if it's the right type.

user253751
  • 57,427
  • 7
  • 48
  • 90
  • now i may complicate it a bit more ;) `System.out.println(new Double(5.0) instanceof Number);` returns `true`, so why `list.add(new Double(5.0));` does not compile in you code with simmilar error i've described in question? Does `?` in ` extends Number>` stand for something that returns `true` from above print? – Dáve Jul 21 '14 at 10:06
  • 3
    @Dave I guess my explanation sucks then. You can't add a `Double` to a `List`. To use a different example: you can't do `List extends Object> list = new ArrayList(); list.add(new Double(5.0));` – user253751 Jul 21 '14 at 10:14