4.4 ArrayList Fundamentals, Operations, and Array Comparison
Key Takeaways
- java.util.ArrayList is a resizable collection class implementing the List interface that dynamically expands its internal Object[] buffer as elements are added, contrasting with fixed-capacity arrays.
- ArrayList resides in java.util and must be explicitly imported; it uses generics (<E>) to enforce compile-time type safety and supports the diamond operator (<>) introduced in Java 7.
- ArrayList can only store object reference types; specifying a primitive type (e.g., ArrayList<int>) causes a compile-time error, requiring the use of primitive wrapper classes (e.g., ArrayList<Integer>).
- The overloaded remove method distinguishes between remove(int index) and remove(Object o); passing a primitive int literal to an ArrayList<Integer> invokes remove(int index), removing by index rather than value.
- Arrays offer primitive support, minimal memory overhead, and .length field access, whereas ArrayList provides dynamic resizing, rich API methods (add, get, set, remove, size, clear), and .size() method access.
4.4 ArrayList Fundamentals, Operations, and Array Comparison
[!NOTE] Exam Focus: Working with
java.util.ArrayListis a core requirement of Oracle's "Arrays and ArrayLists" topic area. Candidates are tested on import requirements, generic type arguments and the diamond operator (<>), primitive wrapper classes and autoboxing/unboxing hazards, core manipulation methods (add,get,set,remove,size,clear), the dangerousremove(int)vs.remove(Object)overloaded method trap in integer lists, and the structural differences between standard arrays and ArrayLists.
While primitive arrays provide ultra-fast, cache-friendly storage for fixed-size sequences, real-world software frequently deals with data sets whose volume cannot be known in advance. When an application reads user input, processes database query results, or tracks items in an e-commerce shopping cart, the collection must grow and shrink dynamically. For these scenarios, Java provides the java.util.ArrayList class.
1. What Is ArrayList & How Does It Work?
An ArrayList is a resizable array implementation belonging to the Java Collections Framework that implements the java.util.List interface. Under the hood, an ArrayList encapsulates a standard Java array (an Object[] buffer named elementData):
- Dynamic Growth: When an
ArrayListis instantiated with its default constructor (new ArrayList<>()), it starts with an initial capacity (typically 10 elements in Java SE 8). - Automatic Buffer Reallocation: As elements are added and the internal array becomes full, the
ArrayListautomatically creates a new, larger array behind the scenes (typically $1.5\times$ the previous capacity), copies all existing elements into the new buffer, and discards the old array for garbage collection. - High-Level API: Unlike arrays that require manual index bookkeeping,
ArrayListexposes intuitive methods to append, insert, replace, search, and delete elements.
Package Import Requirements
Unlike foundational classes such as String, System, and Math—which reside in java.lang and are imported automatically—ArrayList belongs to the java.util package. You must include an import statement at the top of your source file:
import java.util.ArrayList; // Single-class import
// OR
import java.util.*; // Wildcard package import
Omitting the import statement causes a compilation error: cannot find symbol: class ArrayList.
2. Generics, Type Safety, and the Diamond Operator
Before Java 5, collections stored raw Object references. Programmers had to write error-prone manual casts whenever retrieving items:
// Legacy Pre-Java 5 Raw Type (Avoid in modern Java!)
ArrayList list = new ArrayList();
list.add("Hello");
list.add(42); // Any object allowed
String s = (String) list.get(0); // Requires explicit cast; runtime ClassCastException risk
Generics Syntax
Java 5 introduced Generics, allowing developers to specify the exact element type allowed in a collection using angle brackets (<Type>):
ArrayList<String> names = new ArrayList<String>();
names.add("Alice");
// names.add(100); // COMPILE ERROR: incompatible types: int cannot be converted to String
String first = names.get(0); // No cast required! Fully type-safe at compile time.
The Diamond Operator (<>)
Starting in Java 7 and continuing in Java SE 8, the compiler supports type inference for generic instance creation via the diamond operator (<>). Because the compiler already knows the element type from the variable declaration on the left-hand side, you can omit the type parameter on the right-hand side:
ArrayList<String> names = new ArrayList<>(); // Clean, idiomatic Java SE 8 syntax
3. The Object-Only Rule & Primitive Wrapper Classes
A critical rule governing Java collections is:
[!IMPORTANT] The Reference-Only Rule: Generic type arguments can only be reference types (objects). You can never specify a primitive data type as a generic parameter.
ArrayList<int> numbers = new ArrayList<int>(); // COMPILE ERROR!
ArrayList<double> prices = new ArrayList<double>(); // COMPILE ERROR!
ArrayList<boolean> flags = new ArrayList<boolean>(); // COMPILE ERROR!
Attempting to compile any of the above lines triggers a compilation error: unexpected type: found int, required: reference.
To store numbers, characters, or boolean values inside an ArrayList, you must use Java's Primitive Wrapper Classes located in java.lang:
| Primitive Type | Corresponding Wrapper Class (in java.lang) | Parsing Helper Method |
|---|---|---|
byte | Byte | Byte.parseByte("10") |
short | Short | Short.parseShort("50") |
int | Integer (Fully spelled out!) | Integer.parseInt("100") |
long | Long | Long.parseLong("5000") |
float | Float | Float.parseFloat("3.14") |
double | Double | Double.parseDouble("99.99") |
char | Character (Fully spelled out!) | (None; use str.charAt(0)) |
boolean | Boolean | Boolean.parseBoolean("true") |
[!WARNING] Spelling Trap on Exam 1Z0-811: Pay strict attention to the spelling of the wrapper classes. While six wrappers simply capitalize the primitive name, two are fully spelled out:
intwraps toInteger(NOTInt)charwraps toCharacter(NOTChar)Writing
ArrayList<Int>orArrayList<Char>causes a compilation error because no classes namedIntorCharexist in the standard Java library!
All eight primitive wrapper classes are immutable. Once an Integer object is created with value 42, its internal state can never be modified.
4. Autoboxing, Unboxing, and the NullPointerException Trap
Converting between primitives and wrapper objects is automated by the Java compiler:
- Autoboxing: Automatic conversion from a primitive type to its wrapper object (e.g.,
Integer boxed = 10;becomesInteger.valueOf(10)). - Unboxing: Automatic conversion from a wrapper object back to its primitive value (e.g.,
int val = boxed;becomesboxed.intValue()).
ArrayList<Integer> scores = new ArrayList<>();
scores.add(95); // Autoboxing: compiler wraps 95 into Integer.valueOf(95)
int top = scores.get(0); // Unboxing: compiler calls scores.get(0).intValue()
The NullPointerException Unboxing Trap
Because wrapper variables are object references, they can hold null. A primitive variable, however, can never hold null.
When Java attempts to unbox a wrapper reference that happens to be null, a runtime NullPointerException occurs:
Integer count = null; // Legal: object reference assigned null
int total = count; // Compiles cleanly, BUT throws NullPointerException at runtime!
The compiler translates int total = count; into int total = count.intValue();. Invoking .intValue() on a null reference causes an immediate crash. The exact same trap occurs when an ArrayList stores a null entry:
ArrayList<Double> rates = new ArrayList<>();
rates.add(null); // Legal: ArrayList stores references
double r = rates.get(0); // Throws NullPointerException during unboxing!
5. Core ArrayList Manipulation Methods
The 1Z0-811 exam evaluates candidate mastery of the following core ArrayList methods:
Adding Elements: add(E e) and add(int index, E e)
boolean add(E element): Appends the specified element to the end of the list. Always returnstrue.void add(int index, E element): Inserts the element at the specified zero-based index. Elements at and above that position are shifted one position to the right. Returnsvoid.
ArrayList<String> letters = new ArrayList<>();
letters.add("A"); // [A] - appends to end
letters.add("C"); // [A, C] - appends to end
letters.add(1, "B"); // [A, B, C] - inserts at index 1; shifts 'C' right
Accessing & Replacing Elements: get(int index) and set(int index, E e)
Unlike arrays that use brackets (arr[i]), ArrayList uses method calls:
E get(int index): Returns the element at the specified index without modifying the list.E set(int index, E element): Replaces the element at the specified index. Crucial detail:set()returns the element that was replaced (the old value), and does not change thesize()of the list!
ArrayList<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Blue");
String old = colors.set(1, "Green"); // Replaces "Blue" with "Green"
System.out.println(old); // Prints: Blue (the replaced element!)
System.out.println(colors.get(1)); // Prints: Green
System.out.println(colors.size()); // Prints: 2 (size unchanged)
[!CAUTION] The
set()Boundary Trap: You cannot useset()to append elements or grow anArrayList! Callingcolors.set(colors.size(), "Yellow")throws anIndexOutOfBoundsException. To add a new element, you must calladd(), notset().
Checking Size & State: size(), isEmpty(), and clear()
int size(): Returns the number of elements currently stored in the list. Remember:size()is a method with parentheses, unlike arraylengthwhich is a field!boolean isEmpty(): Returnstrueifsize() == 0, otherwisefalse.void clear(): Removes all elements from the list, resettingsize()to 0.
Searching Elements: contains(Object o) and indexOf(Object o)
boolean contains(Object o): Returnstrueif the list contains an element matching the argument (evaluated via.equals()).int indexOf(Object o): Returns the 0-based index of the first occurrence, or-1if not found.
6. The Overloaded remove() Method Trap in ArrayList<Integer>
ArrayList defines two heavily tested overloaded remove methods:
E remove(int index): Removes the element at the specified index, shifts subsequent elements left, and returns the removed element.boolean remove(Object o): Removes the first occurrence of the specified object (matching via.equals()), and returnstrueif an element was found and removed.
The Integer List Trap
Consider what happens when working with an ArrayList<Integer>:
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10); // Index 0
numbers.add(20); // Index 1
numbers.add(30); // Index 2
numbers.remove(1); // WHICH METHOD OVERLOAD EXECUTES?
[!WARNING] Primitive Match Rule: Because the literal
1is a primitiveint, the compiler selects the exact primitive parameter match:remove(int index), NOTremove(Object o)!Therefore,
numbers.remove(1)removes the element at index 1 (which is20), NOT the number1! After execution,numberscontains[10, 30].
To remove the integer value 1 as an object, you must explicitly pass an Integer object reference or cast to Object:
numbers.remove(Integer.valueOf(1)); // Calls remove(Object): searches for value 1
numbers.remove((Object) 1); // Calls remove(Object): searches for value 1
7. Boundary Safety: IndexOutOfBoundsException
Accessing an invalid index in an ArrayList throws an unchecked java.lang.IndexOutOfBoundsException (the superclass of ArrayIndexOutOfBoundsException).
Notice the difference in valid index boundaries across methods:
| Method Call | Valid Index Range | What Triggers IndexOutOfBoundsException? |
|---|---|---|
list.get(index) | $0 \le \text{index} < \text{size()}$ | index < 0 or index >= list.size() |
list.set(index, elem) | $0 \le \text{index} < \text{size()}$ | index < 0 or index >= list.size() |
list.remove(index) | $0 \le \text{index} < \text{size()}$ | index < 0 or index >= list.size() |
list.add(index, elem) | $0 \le \text{index} \le \text{size()}$ | index < 0 or index > list.size() |
For add(index, elem), index == size() is completely legal—it simply appends the element to the end of the list.
8. Architectural Comparison: Array vs. ArrayList
The 1Z0-811 examination frequently tests candidate ability to contrast standard Java arrays with ArrayList. The following table summarizes every key architectural dimension:
| Architectural Dimension | Standard Java Array (type[]) | Java ArrayList<E> |
|---|---|---|
| Capacity / Resizing | Fixed upon instantiation; immutable length. | Dynamic; automatically expands as elements are added. |
| Supported Data Types | Both primitives (int[]) and objects (String[]). | Objects only (ArrayList<String>); primitives require wrappers (ArrayList<Integer>). |
| Element Count | .length (public final field, no parentheses). | .size() (public instance method, with parentheses). |
| Element Access | Square bracket syntax: arr[i]. | Accessor method: list.get(i). |
| Element Modification | Subscript assignment: arr[i] = val;. | Mutator method: list.set(i, val);. |
| Element Insertion | Cannot insert without manual array reallocation and copying. | Built-in method: list.add(index, val); (shifts right). |
| Element Deletion | Slots can be cleared to 0/null, but array does not shrink. | Built-in method: list.remove(index); (shifts left). |
| Memory Structure | Contiguous heap memory block; cache-friendly. | Internal Object[] buffer containing object references. |
| Memory Overhead | Minimal; raw primitive bits or reference addresses. | Higher; buffer capacity padding + wrapper object overhead. |
| Performance | Extremely fast; direct CPU offset calculations. | Slightly slower due to method calls and autoboxing. |
| Printing Output | Inherited toString() prints hashcode: [I@15db9742. | Overridden toString() prints readable contents: [A, B, C]. |
| Out-of-Bounds Error | ArrayIndexOutOfBoundsException | IndexOutOfBoundsException |
| Generics Support | Covariant; does not support generic type parameters. | Invariant; fully integrated with Java Generics. |
Consider the following code snippet:
What is printed to the console when this code executes?ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.remove(1);
System.out.println(numbers);
Consider the following code snippet:
What is the result when this code is compiled and executed?ArrayList<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.set(2, "C++");
System.out.println(languages.size());
Which of the following statements accurately characterizes the differences between Java arrays and java.util.ArrayList?