I will try to answer this in detail. Before generics we were having only List
(a raw list) and it can hold almost anything we can think of.
List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
@Override
public void run() {
// do some work.
}
});
The major problem with the raw list is when we want to get any element out of such list it can only guarantee that it would be Object
and for that reason we need to use casting as:
Object item = rawList.get(0); // we get object without casting.
String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.
So conclusion is a List
can store Object (almost everything is Object in Java) and always returns an Object.
Generics
Now lets talk about generics. Consider the following example:
List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);
In the above case we cannot insert anything other than String
in stringsList
as Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. And we get error when we try to insert a Car
instance in it. Also it eliminates cast as you can check when we invoke
get method. Check this link for understanding why we should use generics.
List<Object>
If you read about type erasure then you will understand that List<String>, List<Long>, List<Animal>
etc. will be having different static types at compile time but will have same dynamic type List
at run time.
If we have List<Object>
then it can store only Object
in it and almost everything is Object
in Java. So we can have:
List<Object> objectList = new ArrayList<Object>();
objectList.add("String Item");
objectList.add(new Car("VW"));
objectList.add(new Runnable() {
@Override
public void run() {
}
});
Object item = objectList.get(0); // we get object without casting as list contains Object
String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.
It seems List<Object>
and List
are same but actually they are not. Consider the following case:
List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.
You can see we can assign any list to raw list and major reason for that is to allow backward compatibility. Also List<String>
will be converted to List
at run time due to type erasure and assignment will be fine anyways.
But List<Object>
means it can only refer to a list of objects and can also store objects only. Even though String
is subtype of Object
we cannot assign List<String>
to List<Object>
as generics are not covariant like arrays. They are invariant. Also check this link for more. Also check the difference between List
and List<Object>
in this question.
List<?>
Now we are left with List<?>
which basically means list of unknown type and can refer to any list.
List<?> crazyList = new ArrayList<String>();
List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
crazyList = stringsList; // fine
The character ?
is known as wildcard and List<?>
is a list of unbounded wildcard. There are certain points to observe now.
We cannot instantiate this list as the following code will not compile:
List<?> crazyList = new ArrayList<?>(); // any list.
We can say a wildcard parameterized type is more like an interface type as we can use it to refer to an object of compatible type but not for itself.
List<?> crazyList2 = new ArrayList<String>();
We cannot insert any item to it as we don't know what actually the type would be.
crazyList2.add("Apple"); // error as you dont actually know what is that type.
Now question arises When would I want to use List<?>
?
You can think of this as a read-only list where you don't care about the type of the items. You can use it to invoke methods like returning the length of the list, printing it etc.
public static void print(List<?> list){
System.out.println(list);
}
You can also check the difference between List, List<?>, List<T>, List<E>, and List<Object>
here.