Difference for <? super/extends String> in meth

2019-03-18 21:47发布

问题:

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.

回答1:

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:

  • What is PESC?
  • What is the difference between super and extends in Java wildcards?


回答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.