Difference for <? super/extends String> in method and variable declaration
Asked Answered
T

2

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.

Twist answered 21/7, 2014 at 9:30 Comment(2)
possible duplicate of difference between <? super T> and <? extends T> in JavaGoodrich
possible duplicate of Java Generics: What is PECS?Luxury
W
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:

Witching answered 21/7, 2014 at 9:39 Comment(6)
Actually your explanation of "C" is wrong. You cannot add an Object to a list<? super String>. The list could be a List<String> during runtime, so this will fail.Stoll
I disagree. Try List<? super String> list = new ArrayList<Object>(); and pass it to the addString method.Witching
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());Stoll
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<Object>, not that you can add Objects :)Witching
touche. How did you remove that comment about "add" - even from the revision history :PStoll
I think that PECS is the explanation i was looking for.Berenice
F
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.

Filmore answered 21/7, 2014 at 9:35 Comment(2)
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?Berenice
@Dave I guess my explanation sucks then. You can't add a Double to a List<I don't know what goes here except that it extends Number>. To use a different example: you can't do List<? extends Object> list = new ArrayList<String>(); list.add(new Double(5.0));Filmore

© 2022 - 2024 — McMap. All rights reserved.