4

----------- lasted update ---------

I think maybe List<?> and List<E> are the same type when we declare them as a method argument. I tried to convert List<E> to List<?>, it worked fine too:

public static <E> void swap2(List<E> list, int i, int j){
    swapHelper2(list, i ,j);
}

private static void swapHelper2(List<?> list, int i, int j) {
    ...
}

------------ original question ------------

From 《Effective Java》 Item 31 it says:

The idea is to write a private helper method to capture the wildcard type...

'private helper method':

public static void swap(List<?> list, int i, int j) {
    swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) { 
    list.set(i, list.set(j, list.get(i)));
}

My question is why List<?> could convert to List<E> through method invocation without compile error?

In my opinion I think List<?> is a SuperType of any List<E>, so it should need some narrowing conversion such as:

List<?> list = new ArrayList<>();
List<String> listOfString = (List<String>)list;
swapHelper(listOfString, 0 ,0);
helpmeRein
  • 43
  • 5
  • Does this answer your question? [Is List a subclass of List? Why are Java generics not implicitly polymorphic?](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po) – Alays Sep 03 '21 at 13:26
  • `swapHelper` works with lists of any type. `swap` takes a list of *some specific but unspecified* type, which falls under the remit of "any". – Michael Sep 03 '21 at 13:27
  • Can you think of a single argument you could pass to `swap` which you would not want to be allowed to be passed along to `swapHelper`? If you answered no, that's why this is not a compiler error. – Michael Sep 03 '21 at 13:30
  • @Alays I don't think they are the same. In brief, `List Question` is about what's the difference between List and List? and why java can't treat List as a subtype of List. My question is what's the difference between List> and List?In method invocation conversion why List> can convert to List without transforming its type to List, but in variable assignment conversion context(as the example above),we need (List) to avoid compile error. – helpmeRein Sep 04 '21 at 02:27
  • @Michael I partly agree with you. But I think maybe `List>` and `List` are the same types when we declare them as method arguments, because I tried convert List to List>, it worked too. – helpmeRein Sep 04 '21 at 03:03

2 Answers2

6

What does List<?> really represents? It means "a list containing some unknown type of objects".

What does a method say when it has a type argument <E> as your swapHelper? It says: I accept any arbitrary type E.

We know that the ? above is a some type, we just don't know which one. The <E> accepts any type, so we know that whatever ? represents it can be used as E. That's why you can call swapHelper with a List<?>.

Why can't we do that trick by simply assigning a List<?> variable to a List<E> variable? Because a local variable doesn't have type arguments, those only exist on methods and classes.

So the fact that swapHelper has the unrestricted type variable makes it able to accept a List<?> without anything in the code ever "knowing" what the ? really represents, since it only cares that there is a type, not what that type is.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • Actual question on your answer: why not declare `swap` as taking a type of `List`? In this case wouldn't that accomplish the same thing as `List>` as well as being a type that could be swapped directly? Maybe there's some reason, I haven't tested it yet. – markspace Sep 03 '21 at 14:12
  • Yes, `swap` could equally just be declared as having a type argument and accepting a `List`. But that would hide the very relevant fact, that even a `List>` can be referenced as a `List` under the right circumstances. A `List>` is not a "one-way street". Basically an unrestricted type argument like on `swapHelper` works very similarly to a wildcard type declaration. The small, but important difference is that with the type argument you can actually refer to and use the type `E` in your code (implicitly or explicitly) – Joachim Sauer Sep 03 '21 at 14:19
  • Got it, thanks for the pedagogical answer. – markspace Sep 03 '21 at 14:19
  • @markspace ref from 《Effective Java》 > In a public API, the second(`public static void swap(List> list, int i, int j)`) is better because it’s simpler. – helpmeRein Sep 04 '21 at 02:41
  • @JoachimSauer Got it! Thanks! – helpmeRein Sep 04 '21 at 02:42
4

List<?> means "a List whose element type isn't known here".

That's not to say that the element type isn't known at all, just, at this point in the code, you don't know what it is.

But all Lists have some element type (unless they are raw, I suppose). So, a List<?> has some element type, if only you knew what it was.

When you say List<E>, you still don't really know what the type of the element is. You know that it's a subtype of Object, but nothing more than that. However, significantly, you know that if you get an element out of a List<E>, then it's a thing of type E:

E item = list.get(i);

and you know that it would be safe to put that thing back into the list:

list.set(j, item);

You don't know what type E is - but you do know that it's the same type as the elements in the list.

So, you can always pass a List<?> to a generic method (that is, it declares its own type variables) that expects a List<E>, because you're saying that the elements have a consistent type, even if you don't know what it is.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243