List in Java: ArrayList, LinkedList, Vector and Stack

A List keeps insertion order and allows duplicates. ArrayList is the default; LinkedList wins only at the ends.

What a List is

A List is an ordered collection. Elements keep their position, duplicates are allowed, and every element is reachable by an index starting at zero.

List<String> topics = new ArrayList<>();

topics.add("variables");
topics.add("loops");
topics.add(1, "operators");     // insert at a position
topics.add("loops");            // duplicates are fine

System.out.println(topics);     // [variables, operators, loops, loops]
System.out.println(topics.get(2));
System.out.println(topics.indexOf("loops"));      // 2, the first match
System.out.println(topics.lastIndexOf("loops"));  // 3

topics.set(0, "data types");
topics.remove("loops");         // removes the first match
topics.remove(0);               // removes by index
remove(int) and remove(Object) are different overloads. On a List<Integer>, list.remove(2) removes the element at index 2, while list.remove(Integer.valueOf(2)) removes the value 2. This is a classic interview trap and a real bug.

ArrayList

Backed by an array that is replaced with a larger one when it fills. Access by index is a direct array read.

List<Integer> scores = new ArrayList<>(100);   // pre-sized, avoids regrowth
OperationCost
get(i), set(i, e)O(1)
add(e) at the endO(1) amortised
add(i, e), remove(i)O(n), elements shift
contains, indexOfO(n)

LinkedList

A doubly linked list. Each element is a node holding the value and links to its neighbours.

OperationCost
addFirst, addLast, removeFirst, removeLastO(1)
get(i)O(n), it walks the chain
add(i, e) once positionedO(1), but reaching the position is O(n)
// Very slow: each get walks from an end
LinkedList<String> list = new LinkedList<>(data);
for (int i = 0; i < list.size(); i++) {
    process(list.get(i));          // O(n) each time, O(n squared) overall
}

// Correct for a linked list
for (String value : list) {
    process(value);                // one pass
}

ArrayList compared with LinkedList

AspectArrayListLinkedList
Index accessO(1)O(n)
Insert or remove at the endO(1) amortisedO(1)
Insert or remove at the frontO(n)O(1)
Memory per elementLowHigher, two links per node
Cache behaviourGood, contiguousPoor, scattered nodes
Also implementsRandomAccessDeque

In practice ArrayList wins almost every time, because contiguous memory is far friendlier to the processor cache than pointer chasing. When you genuinely need queue behaviour at both ends, ArrayDeque beats LinkedList as well.

Vector and Stack

Vector<String> old = new Vector<>();     // synchronised, legacy
Stack<String> stack = new Stack<>();     // extends Vector, legacy

Deque<String> modern = new ArrayDeque<>();   // use this for a stack
modern.push("first");
modern.push("second");
System.out.println(modern.pop());        // second

Vector synchronises every method, which costs time and still does not make a sequence of calls atomic. Stack extends it and therefore exposes add(index, element), letting a caller insert into the middle of a stack. Both are kept only for compatibility.

Useful operations

List<String> names = new ArrayList<>(List.of("Ravi", "Anita", "Meera"));

Collections.sort(names);                       // natural order
names.sort(Comparator.comparing(String::length));
Collections.reverse(names);
Collections.shuffle(names);

List<String> view = names.subList(0, 2);       // a live view, not a copy
List<String> readOnly = Collections.unmodifiableList(names);

names.replaceAll(String::toUpperCase);
names.removeIf(name -> name.startsWith("A"));

subList returns a view. Changes through it affect the original, and structurally modifying the original invalidates it.

Removing safely while iterating

// Throws ConcurrentModificationException
for (String name : names) {
    if (name.isBlank()) {
        names.remove(name);
    }
}

// Correct options
names.removeIf(String::isBlank);

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().isBlank()) {
        it.remove();
    }
}

Common mistakes

  • Confusing remove(int) with remove(Object) on a list of integers.
  • Using LinkedList with an indexed loop.
  • Calling add on the fixed size list from Arrays.asList.
  • Modifying a list during an enhanced for loop.
  • Choosing Vector for thread safety instead of a proper concurrent collection.

Best practices

  • Default to ArrayList.
  • Declare the variable as List.
  • Pre-size when the count is known and large.
  • Use removeIf rather than manual iteration.
  • Return List.copyOf or an unmodifiable view from getters.

Practice

  1. Predict the result of list.remove(1) on a List<Integer> containing 10, 20, 30, then remove the value 20.
  2. Measure an indexed loop over 100000 elements in both implementations and explain the difference.
  3. Remove every empty string from a list in two different correct ways.
  4. Why does Stack allow inserting into the middle, and why is that a problem?
  5. Sort a list of names by length and then alphabetically for equal lengths.

Conclusion

Use ArrayList unless you have a measured reason not to, keep the variable typed as List, remember the remove overload trap, and never structurally modify a list while a plain loop is walking it.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.