W3docs

Java List Interface

Ordered, index-accessible collections in Java with the List interface and its core operations.

Java List Interface

List<E> is Collection<E> plus two extra commitments: the elements have a defined order, and that order is addressable by integer index. Once you have order and an index, a whole class of methods becomes meaningful — get(i), set(i, x), indexOf(x), subList(from, to), sort, iterate in reverse. This chapter walks the contract; the implementations (ArrayList, LinkedList, Vector) come immediately afterwards with their own performance trade-offs.

What "ordered" means here

"Ordered" on a List means insertion order is preserved — index 0 is the first element you put in, index size() - 1 is the last, and adding a new element at the end shifts nothing. It is not "sorted" — a list keeps whatever order you produce. If you want sorted iteration, you either call Collections.sort(list) (which mutates), or use TreeSet / TreeMap from the start. Don't conflate the two.

Duplicates are allowed. [1, 1, 2, 1] is a perfectly legal List<Integer>.

The methods List adds on top of Collection

Everything Collection declares is still there — add, remove, contains, size, etc. List then adds positional and order-aware operations:

Positional access

  • E get(int index) — element at index.
  • E set(int index, E element) — replace, returning the old value.
  • void add(int index, E element) — insert (shifts later elements right).
  • E remove(int index) — remove by position (returns the removed element). Note the overload with Objectlist.remove(1) calls the int version; list.remove(Integer.valueOf(1)) calls the Object version.

Search

  • int indexOf(Object o) — first occurrence, or -1.
  • int lastIndexOf(Object o) — last occurrence, or -1.

Sub-views and iteration

  • List<E> subList(int fromIndex, int toIndex) — a live view of a range. Modifying it modifies the backing list (and vice versa). Half-open: [from, to).
  • ListIterator<E> listIterator() / listIterator(int index) — iterator that can also walk backwards, get the current index, and set / add at the cursor. The ListIterator chapter covers it.

Bulk mutation tied to order

  • default void replaceAll(UnaryOperator<E> op) — apply op to every element in place.
  • default void sort(Comparator<? super E> c) — sort the list using c (or natural order if null).
  • boolean addAll(int index, Collection<? extends E> c) — insert a whole collection at index.

Factories (Java 9+)

  • List.of(...) — an unmodifiable list of the given elements. Compact, allocation-free for tiny sizes.
  • List.copyOf(Collection) — an unmodifiable snapshot of another collection.

Equality on List is order-sensitive

Two lists are equal iff they have the same size, in the same order, with equal elements at every index. List.of(1, 2) does not equal List.of(2, 1), even though as Sets they would. That's a hard rule from the List contract — if you find yourself comparing two lists and getting false when you "shouldn't," check the order first.

subList is a view, not a copy

This trips up almost every learner once:

List<Integer> xs = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));
List<Integer> middle = xs.subList(2, 5);    // [2, 3, 4]
middle.set(0, 99);
System.out.println(xs);          // [0, 1, 99, 3, 4, 5]   — xs changed!
middle.clear();
System.out.println(xs);          // [0, 1, 5]            — gone from xs

subList returns a live window. Reads and writes go through to the backing list. That's hugely useful for in-place algorithms — clear a range, sort a range, insert a range — but it also means you can't keep a subList reference around and then mutate the parent through any other path. The Javadoc says structural changes to the backing list outside the sub-list "undefine" the sub-list's behaviour. In practice, ConcurrentModificationException on the very next call.

If you want an independent slice, copy it: new ArrayList<>(xs.subList(2, 5)).

The two remove overloads

A common bug:

List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);                       // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10));     // removes the value 10 → [30]

The int overload wins because int is more specific than Integer. If you mean "remove the value 10," box it explicitly. This is one of the few places in the language where the autoboxing rules and overload resolution actively conflict.

Sorting in place

list.sort(comparator) mutates the list. Pass null to use the elements' natural order (their Comparable); pass a Comparator otherwise. This is the modern form — Collections.sort(list) still works and is identical, but list.sort(...) is the default List method:

List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(null);                              // natural: ["Ada", "Grace", "Linus"]
names.sort(Comparator.comparingInt(String::length));  // shortest first

The Comparable / Comparator chapter later in this part is the rulebook for what null means and how to build comparators for your own types.

Immutable factories: when add throws

List.of(...), List.copyOf(...), and the lists returned by Collectors.toUnmodifiableList() are unmodifiable. They reject every mutating call with UnsupportedOperationException. They also reject null elements. They're ideal for read-only data shared widely:

List<String> CONSTANTS = List.of("red", "green", "blue");
CONSTANTS.add("yellow");    // throws UnsupportedOperationException

If you might want to mutate later, start with new ArrayList<>(List.of(...)).

A worked example: every List-specific method

The program below exercises the methods List adds beyond Collection. Watch the subList mutation propagate, the overload trap, and the difference between sort and replaceAll.

java— editable, runs on the server

A few takeaways from the output you should hold onto:

  • remove(1) removed 20 (the value at index 1); remove(Integer.valueOf(10)) removed 10 by value. Same method name, two different jobs based on the static type of the argument.
  • After mid.clear(), the parent list is [0, 1, 5]. The view was the range — clearing it removed those elements from the backing array.
  • replaceAll keeps the list the same length and rewrites each element in place; sort rearranges what's already there. They compose well.

What's next

You now know the List contract — what's guaranteed, what mutates what, where the foot-guns are. Time to meet the implementation you'll use 90% of the time: the resizable-array-backed ArrayList. Same contract, specific performance characteristics, and a few extras of its own.

Practice

Practice

`xs` is `new ArrayList<>(List.of(10, 20, 30, 40))`. You call `xs.subList(1, 3).clear()`. What is `xs` afterwards?