[java] Difference between List, List<?>, List<T>, List<E>, and List<Object>

What are the differences between List, List<?>, List<T>, List<E>, and List<Object>?

1. List

List: is a raw type, therefore not typesafe. It will only generate a runtime error when the casting is bad. We want a compile time error when the cast is bad. Not recommended to use.

2. List<?>

List<?> is an unbounded wildcard. But I'm not sure what it's for? I can print a List<?> without issue:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

Why can't I add items to a List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. List<T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

I don't understand this syntax. I saw something like this, and it works:

public <T> T[] toArray(T[] a){
    return a;   
}

Sometimes, I see <T>, or <E>, or <U>, <T,E>. Are they all the same or do they represent something different?

4. List<Object>

This gives the error "The method test(List<Object>) is not applicable for the argument List<String>":

public static void test(List<Object> list){
    System.out.println(list);
}

If I try this then I got "Cannot cast from List<String> to List<Object>":

test((List<Object>) names);

I am confused. String is a subclass of Object, so why isn't List<String> a subclass of List<Object>?

This question is related to java generics

The answer is


The notation List<?> means "a list of something (but I'm not saying what)". Since the code in test works for any kind of object in the list, this works as a formal method parameter.

Using a type parameter (like in your point 3), requires that the type parameter be declared. The Java syntax for that is to put <T> in front of the function. This is exactly analogous to declaring formal parameter names to a method before using the names in the method body.

Regarding List<Object> not accepting a List<String>, that makes sense because a String is not Object; it is a subclass of Object. The fix is to declare public static void test(List<? extends Object> set) .... But then the extends Object is redundant, because every class directly or indirectly extends Object.


Let us talk about them in the context of Java history ;

  1. List:

List means it can include any Object. List was in the release before Java 5.0; Java 5.0 introduced List, for backward compatibility.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

? means unknown Object not any Object; the wildcard ? introduction is for solving the problem built by Generic Type; see wildcards; but this also causes another problem:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Means generic Declaration at the premise of none T or E type in your project Lib.

  1. List< Object> means generic parameterization.

I would advise reading Java puzzlers. It explains inheritance, generics, abstractions, and wildcards in declarations quite well. http://www.javapuzzlers.com/


You're right: String is a subset of Object. Since String is more "precise" than Object, you should cast it to use it as an argument for System.out.println().


For the last part: Although String is a subset of Object, but List<String> is not inherited from List<Object>.


Theory

String[] can be cast to Object[]

but

List<String> cannot be cast to List<Object>.

Practice

For lists it is more subtle than that, because at compile time the type of a List parameter passed to a method is not checked. The method definition might as well say List<?> - from the compiler's point of view it is equivalent. This is why the OP's example #2 gives runtime errors not compile errors.

If you handle a List<Object> parameter passed to a method carefully so you don't force a type check on any element of the list, then you can have your method defined using List<Object> but in fact accept a List<String> parameter from the calling code.

A. So this code will not give compile or runtime errors and will actually (and maybe surprisingly?) work:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. This code will give a runtime error:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. This code will give a runtime error (java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

In B, the parameter set is not a typed List at compile time: the compiler sees it as List<?>. There is a runtime error because at runtime, set becomes the actual object passed from main(), and that is a List<String>. A List<String> cannot be cast to List<Object>.

In C, the parameter set requires an Object[]. There is no compile error and no runtime error when it is called with a String[] object as the parameter. That's because String[] casts to Object[]. But the actual object received by test() remains a String[], it didn't change. So the params object also becomes a String[]. And element 0 of a String[] cannot be assigned to a Long!

(Hopefully I have everything right here, if my reasoning is wrong I'm sure the community will tell me. UPDATED: I have updated the code in example A so that it actually compiles, while still showing the point made.)


In your third point, "T" cannot be resolved because its not declared, usually when you declare a generic class you can use "T" as the name of the bound type parameter, many online examples including oracle's tutorials use "T" as the name of the type parameter, say for example, you declare a class like:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

you are saying that FooHandler's operateOnFoo method expects a variable of type "T" which is declared on the class declaration itself, with this in mind, you can later add another method like

public void operateOnFoos(List<T> foos)

in all the cases either T, E or U there all identifiers of the type parameter, you can even have more than one type parameter which uses the syntax

public class MyClass<Atype,AnotherType> {}

in your forth ponint although efectively Sting is a sub type of Object, in generics classes there is no such relation, List<String> is not a sub type of List<Object> they are two diferent types from the compiler point of view, this is best explained in this blog entry


Problem 2 is OK, because " System.out.println(set);" means "System.out.println(set.toString());" set is an instance of List, so complier will call List.toString();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Problem 3: these symbols are same, but you can give them differet specification. For example:

public <T extends Integer,E extends String> void p(T t, E e) {}

Problem 4: Collection does not allow type parameter covariance. But array does allow covariance.


The reason you cannot cast List<String> to List<Object> is that it would allow you to violate the constraints of the List<String>.

Think about the following scenario: If I have a List<String>, it is supposed to only contain objects of type String. (Which is a final class)

If I can cast that to a List<Object>, then that allows me to add Object to that list, thus violating the original contract of List<String>.

Thus, in general, if class C inherits from class P, you cannot say that GenericType<C> also inherits from GenericType<P>.

N.B. I already commented on this in a previous answer but wanted to expand on it.