Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics, Type Parameters and Wildcards

Tags:

I am trying to understand java generics and they seem extremely difficult to understand. For example, this is fine...

public class Main {

    public static void main(String[] args) {
        List<?> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

... as is this...

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

... and this ...

public class Main {

    public static void main(String[] args) {
        List<List<List<?>>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

... but this doesn't compile:

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

Can someone explain what is going on in simple language?

like image 398
Paul Boddington Avatar asked Dec 14 '14 01:12

Paul Boddington


People also ask

What are wildcards in generics?

The question mark (?) is known as the wildcard in generic programming. It represents an unknown type. The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type.

What is type parameters in generics?

In a generic type or method definition, a type parameter is a placeholder for a specific type that a client specifies when they create an instance of the generic type.

What is a wildcard parameter?

In the Java programming language, the wildcard ? is a special kind of type argument that controls the type safety of the use of generic (parameterized) types. It can be used in variable declarations and instantiations as well as in method definitions, but not in the definition of a generic type.


1 Answers

The main thing to understand with generic types, is that they aren't covariant.

So whilst you can do this:

final String string = "string";
final Object object = string;

The following will not compile:

final List<String> strings = ...
final List<Object> objects = strings;

This is to avoid the situations where you circumvent the generic types:

final List<String> strings = ...
final List<Object> objects = strings;
objects.add(1);
final String string = strings.get(0); <-- oops

So, going through your examples one by one

1

Your generic method takes a List<T>, you pass in a List<?>; which is (essentially) a List<Object>. T can be assigned to the Object type and the compiler is happy.

2

Your generic method is the same, you pass in a List<List<?>>. T can be assigned to the List<?> type and the compiler is again happy.

3

This is basically the same as 2 with another level of nesting. T is still the List<?> type.

4

Here is where it goes a little pear shaped, and where my point from above comes in.

Your generic method takes a List<List<T>>. You pass in a List<List<?>>. Now, as generic types are not covariant, List<?> cannot be assigned to a List<T>.

The actual compiler error (Java 8) is:

required: java.util.List<java.util.List<T>> found: java.util.List<java.util.List<?>> reason: cannot infer type-variable(s) T (argument mismatch; java.util.List<java.util.List<?>> cannot be converted to java.util.List<java.util.List<T>>)

Basically the compiler is telling you that it cannot find a T to assign because of having to infer the type of the List<T> nested in the outer list.

Lets look at this in a little more detail:

List<?> is a List of some unknown type - it could be a List<Integer> or a List<String>; we can get from it as Object, but we cannot add. Because otherwise we run into the covariance issue I mentioned.

List<List<?>> is a List of List of some unknown type - it could be a List<List<Integer>> or a List<List<String>>. In case 1 it was possible to assign T to Object and just not allow add operations on wildcard list. In case 4 this cannot be done - primarily because there is not a generics construct to prevent add to the outer List.

If the compiler were to assign T to Object in the second case then something like the following would be possible:

final List<List<Integer>> list = ...
final List<List<?>> wildcard = list;
wildcard.add(Arrays.asList("oops"));

So, due to covariance, it is not possible to assign a List<List<Integer>> to any other generic List safely.

like image 61
Boris the Spider Avatar answered Oct 25 '22 07:10

Boris the Spider