12.2 Declaring References and Constructing ArrayLists
In the discussion that follows, we assume that any class or interface used from the java.util package has been imported with an appropriate import statement.
The code below illustrates how we can create an empty ArrayList of a specific element type, and assign its reference value to a reference:
ArrayList<String> palindromes = new ArrayList<String>(); // (1)
The element type is specified using angle brackets (<>). The reference palindromes can refer to any ArrayList whose element type is String. The type parameter E of the class ArrayList in Figure 12.1 is replaced by the concrete class String. The compiler ensures that the reference palindromes can only refer to an ArrayList whose elements are of type String, and any operations on this list via this reference are type-safe.
The simplest way to construct an ArrayList is to use the zero-argument constructor to create an empty ArrayList, as shown in the declaration above. The zero-argument constructor creates an empty list with the initial capacity of 10. The capacity of a list refers to how many elements it can contain at any given time, not how many elements are actually in the list (which is called the size). The capacity of a list and its size can change dynamically as the list is manipulated. The Array-List<String> created at (1) can only contain elements of type String.
The assignment in the declaration statement (1) is valid because the types on both sides are assignment compatible—an ArrayList of String. The reference palindromes can now be used to manipulate the ArrayList<String> that it denotes.
We can use the diamond operator (<>) in the ArrayList creation expression on the right-hand side of the declaration statement. In this particular context, the compiler can infer the element type of the ArrayList from the declaration of the reference type on the left-hand side.
ArrayList<String> palindromes = new ArrayList<>(); // Using the diamond operator
However, if the diamond operator is omitted, the compiler will issue an unchecked conversion warning, as shown at (2) in the next code snippet. A new ArrayList is created based on an ArrayList of Integer that is passed as an argument to the constructor. The ArrayList of Integer is created at (1). The reference newList1 of type ArrayList<String> refers to an ArrayList whose element type is Integer, not String. The code at (2) compiles, but we get a ClassCastException at runtime at (3) when we retrieve an element from this list. The get() method call at (3) expects a String in the ArrayList, but gets an Integer. If the diamond operator is used, as shown at (4), the compiler reports a compile-time error, and the problem described at (3) cannot occur at runtime. By issuing an unchecked conversion warning at (2), the compiler alerts us to the fact that it cannot guarantee type-safety of the list created at (2).
ArrayList<Integer> intList = new ArrayList<>(); // (1) ArrayList of Integer
intList.add(10); intList.add(100); intList.add(1000);
ArrayList<String> newList1 = new ArrayList(intList); // (2) Unchecked conversion
// warning
System.out.println(newList1.get(0)); // (3) ClassCastException!
ArrayList<String> newList2 = new ArrayList<>(intList); // (4) Compile-time error!
Best practices advocate programming to an interface. In practical terms, this means using references of an interface type to manipulate objects of a concrete class that implement this interface. Since the class java.util.ArrayList<E> implements the java.util.List<E> interface, the declaration at (1) can be written as shown in the next code snippet. This declaration is valid, since the reference value of a subtype object (ArrayList<String>) can be assigned to a reference of its supertype (List<String>).
List<String> palindromes = new ArrayList<>(); // (2) List<String> reference
This best practice provides great flexibility in substituting other objects for a task when necessary. The current concrete class can easily be replaced by another concrete class that implements the same interface. Only code creating objects needs to be changed. As it happens, the Java Collections Framework provides another implementation of lists: the java.util.LinkedList<E> class, which also implements the List<E> interface. If this class is found to be more conducive for maintaining palindromes in a list, we need simply change the name of the class in declaration (2), and continue using the reference palindromes in the program:
List<String> palindromes = new LinkedList<>(); // Changing implementation.
The ArrayList<E> class also provides a constructor that allows an empty ArrayList to be created with a specific initial capacity.
List<String> palindromes = new ArrayList<>(20); // Initial capacity is 20.
The ArrayList class provides the add(E) method to append an element to the end of the list. The new element is added after the last element in the list, thereby increasing the list size by 1.
palindromes.add(“level”); palindromes.add(“Ada”); palindromes.add(“kayak”);
System.out.println(palindromes);
The print statement calls the toString() method in the ArrayList<E> class to print the elements in the list. This toString() method applies the toString() method of the individual elements to create a text representation in the following default format:
[level, Ada, kayak]
A third constructor allows an ArrayList to be constructed from another collection. The following code creates a list of words from a list of palindromes. The order of the elements in the new ArrayList<String> is the same as that in the Array-List<String> that was passed as an argument in the constructor.
List<String> wordList = new ArrayList<>(palindromes);
System.out.println(wordList); // [level, Ada, kayak]
wordList.add(“Naan”);
System.out.println(wordList); // [level, Ada, kayak, Naan]
The next examples illustrate the creation of empty lists of different types of elements. The compiler ensures that operations on the ArrayList are type-safe with respect to the element type. Declaration (3) shows how we can create nested list structures (i.e., a list of lists), analogous to an array of arrays. Note that the diamond operator is not nested at (3). Declaration (4) shows that the element type cannot be a primitive type; rather, it must be a reference type.
List<StringBuilder> synonyms = new ArrayList<>(); // List of StringBuilder
List<Integer> attendance = new ArrayList<>(); // List of Integer
List<List<String>> listOfLists = new ArrayList<>(); // (3) List of List of String
List<int> frequencies = new ArrayList<>(); // (4) Compile-time error!
When comparing arrays and ArrayLists, there is one other significant difference that concerns the subtype relationship.
Object[] objArray = new String[10]; // (5) OK!
In declaration (5), since String is a subtype of Object, String[] is a subtype of Object[]. Thus we can manipulate the array of String using the objArray reference.
objArray[2] = “Green”; // (6) OK!
objArray[1] = Integer.valueOf(2016); // ArrayStoreException!
The preceding assignment requires a runtime check to guarantee that the assignment is type compatible. Otherwise, an ArrayStoreException is thrown at runtime.
For the ArrayList<E>, the following declarations will not compile:
ArrayList<Object> objList1 = new ArrayList<String>();// (7) Compile-time error!
List<Object> objList2 = new ArrayList<String>(); // (8) Compile-time error!
Although String is a subtype of Object, it is not the case that an ArrayList<String> is a subtype of ArrayList<Object>. If this were the case, we could use the objList1 reference to add other types of objects to the ArrayList of String, thereby jeopardizing its type-safety. Since there is no information about the element type E available at runtime to carry out a type compatibility check, as in the case of arrays, the subtype relationship is not allowed at (7). For the same reason, (8) will also not compile: ArrayList<String> is not a subtype of List<Object>. In general, the subtype covariant relationship does not hold for generic types. The Java language provides wildcards to overcome this restriction (§11.4, p. 579).
The ArrayList<E> constructors are summarized here:
ArrayList()
ArrayList(int initialCapacity)
ArrayList(Collection<? extends E> c)
The zero-argument constructor creates a new, empty ArrayList with an initial capacity of 10.
The second constructor creates a new, empty ArrayList with the specified initial capacity.
The third constructor creates a new ArrayList containing the elements in the specified collection. The declaration of the parameter c essentially means that parameter c can refer to any collection whose element type is E or a subtype of E. The new ArrayList<E> will retain any duplicates. The ordering in the ArrayList<E> will be determined by the traversal order of the iterator for the collection passed as an argument.
In a constructor call, the element type of the list is specified enclosed in angle brackets or by the diamond operator after the class name if it is to be inferred by the compiler. A raw ArrayList is created if the angle brackets are omitted, and the compiler will issue an unchecked warning.
Leave a Reply