Java List Interface

In the last lesson you learned about Java Iterator and Iterable. Now let’s look at the most common shape of collection in everyday Java: the List. It keeps your items in order, lets you store the same value more than once, and lets you reach any item by its position number.

πŸ€” What problem does a List solve?

Picture a music playlist. Songs have an order. You might add the same song twice. And you want to say β€œplay the fourth song now.” That is a List.

Without it, nothing else fits:

  • A plain array holds order, but its size is fixed and it has almost no helpers.
  • A Set would refuse your duplicate song.
  • A Map would force you to invent a key for every song.

A List is an ordered collection that allows duplicates and gives access by index. Order is remembered, repeats are allowed, and every item sits at a numbered position you can jump to.

🧩 Three things that make a List a List

A List makes three promises:

  • It keeps insertion order. The order you add is the order you get back.
  • It allows duplicates. The same value can appear many times.
  • It gives index access. Positions start at 0, so the first item is at index 0.

This example shows all three. We add three names, repeat one, and reach into the middle by position.

import java.util.ArrayList;
import java.util.List;
public class ListBasics {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Riya");
names.add("Alex"); // duplicate allowed
System.out.println(names); // order kept
System.out.println(names.get(1)); // index access
System.out.println(names.size()); // how many items
}
}

When you run this, the output is:

Output

[Alex, Riya, Alex]
Alex
3

The order matches how we added items. Alex shows up twice. And get(1) gave the second item, because counting starts at zero.

πŸ› οΈ The methods beyond Collection

Every List is a Collection, so it has add, remove, size, contains, clear, and loop support. A List adds index-based methods on top, because it knows about positions. The ones worth learning by heart:

  • get(i) returns the item at index i.
  • set(i, e) replaces the item at index i with e and returns the old one.
  • add(i, e) inserts e at index i and shifts the rest to the right.
  • remove(i) removes the item at index i and shifts the rest to the left.
  • indexOf(e) returns the first index where e is found, or -1 if it is missing.
  • lastIndexOf(e) returns the last index where e is found, or -1.
  • subList(from, to) returns a view of part of the list, from from up to but not including to.
  • listIterator() gives you a cursor that can move both forward and backward.

Let’s put the main ones to work. We start with three colors, then read, replace, insert, and remove by position.

import java.util.ArrayList;
import java.util.List;
public class IndexMethods {
public static void main(String[] args) {
List<String> colors = new ArrayList<>();
colors.add("red");
colors.add("green");
colors.add("blue");
System.out.println(colors.get(0)); // read first
colors.set(1, "lime"); // replace second
colors.add(1, "yellow"); // insert at index 1
colors.remove(3); // remove by index
System.out.println(colors);
}
}

Run it and you get:

Output

red
[red, yellow, lime]

What happened, step by step:

  • get(0) printed red, the first item.
  • set(1, "lime") swapped green for lime, so the list became [red, lime, blue].
  • add(1, "yellow") pushed yellow into position one and slid the rest right, giving [red, yellow, lime, blue].
  • remove(3) deleted the item at index three, which was blue, leaving [red, yellow, lime].

Searching with indexOf and lastIndexOf

When a value appears more than once, indexOf finds the first match and lastIndexOf finds the last.

import java.util.List;
import java.util.ArrayList;
public class FindPositions {
public static void main(String[] args) {
List<String> letters = new ArrayList<>(List.of("a", "b", "a", "c", "a"));
System.out.println(letters.indexOf("a")); // first match
System.out.println(letters.lastIndexOf("a")); // last match
System.out.println(letters.indexOf("z")); // not found
}
}

Here is the output:

Output

0
4
-1

The first a is at index 0, the last at index 4. And z is missing, so we get -1. That -1 means β€œnot found”, so always check for it before trusting the result.

Taking a slice with subList

subList(from, to) hands you a window into the list. It starts at from and stops just before to.

import java.util.List;
import java.util.ArrayList;
public class SliceList {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30, 40, 50));
List<Integer> middle = nums.subList(1, 4);
System.out.println(middle);
}
}

The output is:

Output

[20, 30, 40]

So subList(1, 4) gave indexes one, two, and three. Index four is left out because the end is not included. One word of care: a subList is a live view, not a copy. Changing it changes the original list too.

πŸ” Iterating a list

You will loop over lists constantly. There are three clean ways.

import java.util.List;
public class LoopList {
public static void main(String[] args) {
List<String> fruits = List.of("apple", "mango", "pear");
// 1. for-each: simplest, when you just need each item
for (String fruit : fruits) {
System.out.println(fruit);
}
// 2. index loop: when you need the position number
for (int i = 0; i < fruits.size(); i++) {
System.out.println(i + " -> " + fruits.get(i));
}
// 3. forEach with a lambda: short and modern
fruits.forEach(System.out::println);
}
}

The first loop prints each fruit. The second pairs each item with its index. The third does the same as the first in one line. Use for-each by default, and the index loop only when you need the number i.

A listIterator is a fourth option for special cases. It can move backward as well as forward, and add or replace items while you walk. Reach for it when you want to scan in reverse.

πŸ—οΈ The main implementations

List is an interface, so it only describes behavior. To hold data you pick a class that implements it. Three exist, each with a different engine inside:

  • ArrayList is backed by a growable array. Fast for reading by index, and the one you will use almost every time.
  • LinkedList is backed by a chain of linked nodes. Faster for adding or removing at the start.
  • Vector is an older, thread-safe ArrayList. Rarely used in new code, because its locking slows it down.

The short rule: use ArrayList unless you have a clear reason not to. Each has its own lesson. They all behave like a List on the outside, with the same get, set, add, and remove.

🎯 Program to the List interface

When you declare a variable, use the interface type List on the left, and pick the class only on the right.

import java.util.List;
import java.util.ArrayList;
public class ProgramToInterface {
public static void main(String[] args) {
// good: the type is the interface
List<String> tasks = new ArrayList<>();
tasks.add("write report");
System.out.println(tasks);
}
}

Why does this help? Your code depends on the idea of a list, not one exact class. Switch to a LinkedList later by changing a single word, and nothing else breaks. Methods that accept List<String> take any kind of list. This is programming to the interface, and it keeps your code flexible.

πŸ”’ Immutable lists with List.of

Sometimes you want a list that can never change, like a fixed set of weekdays. List.of builds an immutable list, one that is locked and cannot be modified.

import java.util.List;
public class ImmutableList {
public static void main(String[] args) {
List<String> days = List.of("Mon", "Tue", "Wed");
System.out.println(days);
System.out.println(days.get(2));
}
}

The output is plain:

Output

[Mon, Tue, Wed]
Wed

This is perfect for values that should stay fixed, and it makes your intent clear. Any attempt to add or remove from it will fail, which we see in the mistakes below.

⚠️ Common Mistakes

Mixing up remove by index and remove by value

List has two remove methods. remove(int index) deletes by position, remove(Object o) deletes by value. With Integer values, a bare number is read as an index, not a value.

List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
// ❌ you wanted to remove the value 20, but this removes index 20
// (and crashes here because index 20 does not exist)
nums.remove(20);

To remove by value, hand it a real object so Java picks the other method.

List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
// βœ… removes the value 20, not the index
nums.remove(Integer.valueOf(20));
System.out.println(nums); // [10, 30]

Trying to change a List.of list

A list made with List.of is locked. Any add, set, or remove throws an error at runtime.

List<String> days = List.of("Mon", "Tue");
// ❌ this throws UnsupportedOperationException
days.add("Wed");

If you need a list you can change, copy it into a fresh ArrayList first.

List<String> days = new ArrayList<>(List.of("Mon", "Tue"));
// βœ… this is a real, editable list now
days.add("Wed");
System.out.println(days); // [Mon, Tue, Wed]

Reaching for an index that is not there

Indexes run from 0 up to size() - 1. Ask for anything outside that range and the program crashes with an out-of-bounds error.

List<String> items = new ArrayList<>(List.of("a", "b", "c"));
// ❌ size is 3, so the last valid index is 2
System.out.println(items.get(3)); // IndexOutOfBoundsException

Check the size, or use a loop that stops at the right place.

List<String> items = new ArrayList<>(List.of("a", "b", "c"));
// βœ… the loop never goes past the last valid index
for (int i = 0; i < items.size(); i++) {
System.out.println(items.get(i));
}

βœ… Best Practices

  • Declare variables as List, the interface, and pick the class only when you create the object. This keeps your code easy to change later.
  • Reach for ArrayList by default. Switch to LinkedList or Vector only when you have a measured reason.
  • Use the for-each loop unless you truly need the index number.
  • When the data should never change, build it with List.of so the lock is enforced for you.
  • To remove a numeric value, wrap it with Integer.valueOf(...) so you do not accidentally remove by index.
  • Treat -1 from indexOf as β€œnot found” and check for it before using the result.
  • Remember that subList is a live view, not a copy. Wrap it in new ArrayList<>(...) if you want an independent copy.

πŸ“ What You’ve Learned

  • A List is an ordered collection that allows duplicates and gives index access.
  • The index-based methods get, set, add(i, e), remove(i), indexOf, lastIndexOf, subList, and listIterator go beyond what plain Collection offers.
  • ArrayList, LinkedList, and Vector are the three implementations, with ArrayList as your everyday choice.
  • Programming to the List interface keeps your code flexible.
  • List.of builds an immutable list, and trying to change it throws an error.
  • Watch out for remove(int) versus remove(Object), modifying a locked list, and stepping past the last index.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What three things make a List a List?

    Why: A List keeps insertion order, allows duplicate values, and lets you reach items by their index position.

  2. 2

    For a List of Integer, how do you remove the value 20 rather than index 20?

    Why: A bare int calls remove(int index). Wrapping it with Integer.valueOf forces remove(Object) so it removes by value.

  3. 3

    What happens if you call add() on a list made with List.of?

    Why: List.of creates an immutable list, so any change throws UnsupportedOperationException at runtime.

  4. 4

    Why declare a variable as List rather than ArrayList?

    Why: Programming to the List interface means your code depends on the behavior, not one exact class, so swapping is easy.

πŸš€ What’s Next?

You now know what a List promises and how to work with one through its interface. Next we go deep on the implementation you will use most: the resizable, array-backed list with all its handy methods.

Java ArrayList

Share & Connect