Java List Interface — Ordered Collections
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:
- Insertion order is preserved.
- Duplicates are allowed.
- Index-based access — you can get/set elements by position.
- 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. UseLinkedListonly when you need frequent inserts/removes at both ends (as aDeque).
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 canset()but notadd()orremove().
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,
ArrayListwins almost always due to CPU cache locality and lower memory overhead. UseLinkedListonly as aDeque.
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
List= ordered, allows duplicates, index-based access.ArrayListis the default — O(1) random access, excellent cache performance.LinkedListis rarely the right choice for aList— use it as aDeque.- Use
List.of()for immutable lists;new ArrayList<>(List.of(...))for mutable ones. - 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.