Java List Interface — Ordered Collections

3 minute read

The List interface represents an ordered collection (also called a sequence). Unlike Set, a List allows duplicates and provides index-based access to elements.


The List Contract

A List guarantees:

  1. Insertion order is preserved.
  2. Duplicates are allowed.
  3. Index-based access — you can get/set elements by position.
  4. Null elements are allowed (in most implementations).
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Alice");  // duplicate allowed
System.out.println(names);  // [Alice, Bob, Alice]
System.out.println(names.get(1));  // Bob

List Implementations

Implementation Backed By Random Access Insert/Remove Thread-Safe
ArrayList Dynamic array O(1) O(n) middle ❌
LinkedList Doubly-linked list O(n) O(1) ends ❌
Vector Dynamic array O(1) O(n) middle ✅ (legacy)
CopyOnWriteArrayList Copy-on-write array O(1) O(n) ✅

Default choice: ArrayList. Use LinkedList only when you need frequent inserts/removes at both ends (as a Deque).


Common List Methods

Adding Elements

List<String> list = new ArrayList<>();
list.add("A");                // append to end
list.add(0, "B");            // insert at index 0
list.addAll(List.of("C", "D")); // add all
// [B, A, C, D]

Accessing Elements

list.get(0);          // "B" — element at index
list.indexOf("C");    // 2 — first occurrence
list.lastIndexOf("A"); // 1 — last occurrence
list.contains("D");   // true
list.size();          // 4
list.isEmpty();       // false

Modifying Elements

list.set(1, "Z");       // replace at index 1 → returns "A"
list.remove(0);          // remove by index → returns "B"
list.remove("Z");        // remove first occurrence → returns true
list.clear();            // remove all

Sublists

List<String> full = List.of("A", "B", "C", "D", "E");
List<String> sub = full.subList(1, 4);  // [B, C, D] — view, not copy

subList() returns a view — changes to the sublist affect the original list (and vice versa). The returned list is backed by the original.


Immutable Lists (Java 9+)

// Unmodifiable list — Java 9+
List<String> immutable = List.of("A", "B", "C");
// immutable.add("D");  // UnsupportedOperationException

// Copy of existing list — Java 10+
List<String> copy = List.copyOf(mutableList);
Method Version Null Elements Modifiable
List.of(...) Java 9 ❌ ❌
List.copyOf(collection) Java 10 ❌ ❌
Collections.unmodifiableList(list) Java 2 ✅ ❌ (but underlying list can change)

Converting Between List and Array

// Array → List
String[] arr = {"A", "B", "C"};
List<String> list1 = Arrays.asList(arr);       // fixed-size view
List<String> list2 = new ArrayList<>(List.of(arr)); // true mutable copy

// List → Array
String[] back = list2.toArray(new String[0]);
// Java 11+:
String[] back2 = list2.toArray(String[]::new);

Arrays.asList() returns a fixed-size list backed by the array — you can set() but not add() or remove().


Iterating Over a List

List<String> names = List.of("Alice", "Bob", "Charlie");

// 1. Enhanced for-loop
for (String name : names) {
    System.out.println(name);
}

// 2. Index-based (only for RandomAccess lists)
for (int i = 0; i < names.size(); i++) {
    System.out.println(names.get(i));
}

// 3. Iterator
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// 4. forEach + lambda
names.forEach(name -> System.out.println(name));

// 5. Stream
names.stream().filter(n -> n.startsWith("A")).forEach(System.out::println);

Sorting a List

List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob"));

// Natural order
Collections.sort(names);          // [Alice, Bob, Charlie]
names.sort(Comparator.naturalOrder()); // same

// Custom order
names.sort(Comparator.reverseOrder());           // [Charlie, Bob, Alice]
names.sort(Comparator.comparing(String::length)); // by length

ArrayList vs LinkedList — Quick Comparison

Operation ArrayList LinkedList
get(index) O(1) O(n)
add(element) (end) O(1) amortized O(1)
add(index, element) (middle) O(n) O(n)*
remove(index) (middle) O(n) O(n)*
contains(element) O(n) O(n)
Memory overhead Low High (two pointers per node)
Cache locality Excellent Poor

*LinkedList is O(1) for the actual insert/remove, but O(n) to find the position.

In practice, ArrayList wins almost always due to CPU cache locality and lower memory overhead. Use LinkedList only as a Deque.


Common Mistakes

Mistake Problem Fix
Arrays.asList() then add() UnsupportedOperationException Wrap in new ArrayList<>()
Modifying list during iteration ConcurrentModificationException Use Iterator.remove() or removeIf()
Using List of raw types ClassCastException at runtime Always use generics: List<String>
subList() then modify original Undefined behaviour Copy if you need independence

Key Takeaways

  1. List = ordered, allows duplicates, index-based access.
  2. ArrayList is the default — O(1) random access, excellent cache performance.
  3. LinkedList is rarely the right choice for a List — use it as a Deque.
  4. Use List.of() for immutable lists; new ArrayList<>(List.of(...)) for mutable ones.
  5. Program to the interface (List) not the implementation (ArrayList).

What’s Next?

Next: ArrayList Deep Dive — internal array backing, growth strategy, capacity management, and performance tuning.

Updated: