Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics Capture List<?>

Tags:

java

generics

I was looking at the Java Generics documentation and found this piece of code,

public class WildcardError {

void foo(List<?> l) {
    //This give a compile time error
    l.set(0,l.get(0));
}
}

I can understand that we are fetching an element from a List<?> and trying to set it to another List<?>. So the compiler gives an error. My question is it makes sense when the 2 lists are different i.e. l.set(0, m.get(0)) here lists l and m are different. But in the above example, l and l are the same lists. Why isn't the compiler smart enough to see that? Is it hard to implement it?

Edit: I am aware that I can fix it by a helper method or by using T instead of a ?. Just wondering why compiler doesn't do it for me.

like image 289
vikky.rk Avatar asked Jul 16 '12 07:07

vikky.rk


3 Answers

The compiler reports an error because there is no way -- in general -- that it can tell whether two expressions, (in this case l and l) refer to the same list.

Related, somewhat generalized, question:

  • How does the JLS specify that wildcards cannot be formally used within methods?
like image 29
aioobe Avatar answered Sep 27 '22 22:09

aioobe


List<?> means list containing elements of some unknown type, so when one wants to take elements from it using list.get(i) it will return object of some unknown type, so the only valid guess will be Object. Then when one tries to set element back using list.set(index, list.get(index)) it produces compile-time error, since as mentioned above List<?> can only contain some unknown type, so putting Object to it may cause ClassCastException.

This is explained very well in Joshua Bloch's Effective Java, 2nd ed., Item 28: Use bounded wildcards to increase API flexibility

This is also known as PECS principle and good explanation can be found in this Q/A: What is PECS (Producer Extends Consumer Super)? (please note that List<?> is the same as List<? extends Object> with minor exceptions)

In laymans terms, one should use List<?> as method parameter only to get elements from it inside that method, not when one needs to put elements into list. When one needs to both put and get he/she needs to either generify method using type parameter T as in Lukas Eder's answer (type-safe way) or simply use List<Object> (not type-safe way).

like image 34
Yuriy Nakonechnyy Avatar answered Sep 27 '22 22:09

Yuriy Nakonechnyy


In your specific case, you can explicitly fix this:

public class WildcardError {
    <T> void foo(List<T> l) {
        // This will work
        l.set(0, l.get(0));
    }
}

Or if you don't want to change the original API, introduce a delegate helper method:

public class WildcardError {
    void foo(List<?> l) {
        foo0(l);
    }

    private <T> void foo0(List<T> l) {
        // This will work
        l.set(0, l.get(0));
    }
}

Unfortunately, the compiler cannot infer that "obvious" <T> type. I've been wondering about that, too. It seems like something that could be improved in a compiler, as every wild card can be informally translated to an unknown <T> type. Probably, there are some reasons why this was omitted, perhaps this is only intuition, but formally impossible.

UPDATE:

Note, I've just seen this peculiar implementation of Collections.swap():

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

The JDK guys resort to a raw type, in order to handle this, locally. This is a strong statement indicating that this probably should be supported by the compiler, but for some reason (e.g. lack of time to formally specify this) just wasn't done

like image 91
Lukas Eder Avatar answered Sep 27 '22 22:09

Lukas Eder