Tuesday, September 1, 2015

Differences between List, List<Object> and List<?>

We may mistakenly think that List, List<Object> and List<?> are the same because of their similarity behavior. For example, calling the getter method of each of them will return a Java object with java.lang.Object type. Despite their behaviors are similar, they do have different behaviors and carrying different notion and purpose.

List: Raw Type

A list that could contain any Java object as its elements. This is the old way of using Java Collection before Java version 5. It is not type-safe because Java compiler has no way to check and prohibit the getter and setter operations called upon it. It is valid to accept any Java object, and it is the calling code's responsibility to cast its return object/element to the correct type. Doing this casting wrongly can't be revealed during compilation time, and one will get ClassCastException only during runtime. Since Java version 5, we should never make use of raw type. Doing so Java compiler will generate unchecked or unsafe operations warning to indicate there is a risk in the code.

List rawTypeList = new ArrayList();

// Java compiler warns unchecked or unsafe operations
rawTypeList.add(0);
rawTypeList.add("test");

String str = (String) rawTypeList.get(0); // ClassCastException thrown during runtime

The only reason it is still allowed is to support backward compatibility of legacy code.

List<Object>: Parameterized Type

A list that is purposely declared and created to contains Java object (which IS A Object) as its elements. Although it works similar to raw type, but it is type-safe and conceptually different compares to raw type. The Object type argument specified in the List<Object> indicates that this list is purposely used to deal with the element(s) with Object type only. Whoever use this list should aware that its return object/element should be used as a generic object and should never be casted to any specific type. If one still do the manual casting, then he/she is taking his/her own risk of getting ClassCastException during runtime.

List<Object> parameterizedTypeList = new ArrayList<>();

// No warning, Integer and String IS A Object, hence it is a valid input object.
parameterizedTypeList.add(0); 
parameterizedTypeList.add("test"); 

// Do not do this as it against the purpose of using Java Generic.
// ClassCastException thrown during runtime.
String str = (String) parameterizedTypeList.get(0); 

Although List<Object> is a valid parameterized type, but the value it offers not more than a raw type list. Moreover, it is restrictive because Java compiler ensures it can only refer to an instance of List that had parameterized to Object type.

public static int countNonNullItems(List<Object> list) {
    int count = 0;
    for (int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
        if (obj != null) {
            count++;
        }
    }
    return count;
}

public static void main(String[] args) {
    countNonNullItems(new ArrayList<Object>()); // Valid
    countNonNullItems(new ArrayList<Integer>()); // Compilation error: incompatible types
    countNonNullItems(new ArrayList<String>()); // Compilation error: incompatible types
}

Whenever applicable, you could change your method parameter from List<Object> to List<?> to increase method reusability. Refer to next section for more detail.

List<?>: Unbounded Wildcard Type

A List that could contain elements with unknown type. It is used in the operation where this operation does not care about its element type. It is type-safe and actually a special kind of parameterized type where its element type is parameterized to unknown. It only able to accept null as its element, because null has the unknown type. Trying to add an object into this list will cause the compilation error. Why? Because this breaks the intention of wildcard where the element type must be remained unknown.

List<?> unBoundedWildcardTypeList = new ArrayList<>();

unBoundedWildcardTypeList.add(null); // Valid
unBoundedWildcardTypeList.add(1); // Compilation error
unBoundedWildcardTypeList.add("test"); // Compilation error

In term of instance reference, the List<?> is looser than the List<Object>. It is able to refer to an instance of List with any element type. Why? Since the Java compiler blocks the business logic in the operation from working with the list's element type, it does not matter what List instance (with what element type) is given to this operation. This behavior could improve the method reusability, that is why we should take this into consideration when designing our methods.

public static int countNonNullItems(List<?> list) {
    int count = 0;
    for (int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
        if (obj != null) {
            count++;
        }
    }
    return count;
}

public static void main(String[] args) {
    countNonNullItems(new ArrayList<Object>()); // Valid
    countNonNullItems(new ArrayList<Integer>()); // Valid
    countNonNullItems(new ArrayList<String>()); // Valid
}

In fact, there are API in java.util.Collection are using wildcard type as their method parameters such as containsAll(Collection<?> c)removeAll(Collection<?> c)retainAll(Collection<?> c).

Summary

Table below summarized the differences between ListList<Object> and List<?>.


In short, we should avoid from using raw type in the new code. Whenever a method parameter is List<Object>, change it to List<?> to promote the method reusability.


References:
Book: Effective Java 2nd Edition  - Joshua Bloch
Book: Java Generics and Collections - Maurice Naftalin & Philip Wadler
https://docs.oracle.com/javase/tutorial/java/generics/index.html







No comments: