Taking part of a list
Sometimes only part of a list is needed, such as the first few items. Java has no slice syntax like list[1:4]; the method for it is subList(from, to). It returns the items from index from up to, but not including, index to.
List<String> letters = new ArrayList<>(List.of("a", "b", "c", "d", "e"));
System.out.println(letters.subList(1, 4)); // [b, c, d]
The end index is left out, so the length is easy to read off: 4 - 1 is 3 items, the ones at indexes 1, 2 and 3. Two edge cases follow from the rule: subList(2, 2) is an empty list, and to reach the very end you pass the size as the end index:
List<String> tail = new ArrayList<>(letters.subList(3, letters.size()));
System.out.println(tail); // [d, e]
subList is a window, not a copy
The list that subList returns does not hold its own items. It is a view of the same items in the original list, so changing one changes the other:
List<String> view = letters.subList(1, 4);
view.set(0, "X");
System.out.println(letters); // [a, X, c, d, e]
To get a list of your own, copy the view into a new ArrayList: new ArrayList<>(letters.subList(1, 4)). Changes to the copy leave the original alone. A view also stops being safe to use if the original list changes in size: after letters.add("f"), asking an older view for its size() throws ConcurrentModificationException. Copying avoids both problems.
Slices from the end
There are no negative indexes in Java, so the last items are counted from the size. The last two items of letters are letters.subList(letters.size() - 2, letters.size()), which gives [d, e]. Both ends are computed from size(), so the same line works for a list of any length, as long as it has at least two items.
Bad ranges throw
An end index past the size throws IndexOutOfBoundsException (on a five-item list, subList(2, 9) fails with toIndex = 9). A start after the end throws IllegalArgumentException (fromIndex(3) > toIndex(1)).
The exercise
The list runners holds the finishers of a race in order. The first three go on the podium, and everyone else goes in rest.
- Podium: copy
runners.subList(0, 3), the items at indexes 0, 1 and 2, into a new list calledpodium. - Rest: copy
runners.subList(3, runners.size())intorest. The size is one past the last index, so this runs to the end.
List<String> runners = new ArrayList<>(List.of("Ana", "Ben", "Cleo", "Dan", "Eve"));
List<String> podium = new ArrayList<>(runners.subList(0, 3));
List<String> rest = new ArrayList<>(runners.subList(3, runners.size()));
System.out.println(podium.get(0) + " " + podium.get(1) + " " + podium.get(2));
System.out.println(rest.get(0) + " " + rest.get(1));
System.out.println(runners.size());
Output:
Ana Ben Cleo
Dan Eve
5
The last line shows that runners still holds all five names: the copies did not take anything out of it.
Common mistakes
- Off by one on the end index. On the runners list,
subList(0, 2)gives only[Ana, Ben], andsubList(0, 4)gives four names. To get the first three, the end index is 3. - Ending at
size() - 1by habit. The last valid index issize() - 1, but because the end is excluded,subList(3, runners.size() - 1)gives only[Dan]and dropsEve. Usesize()to reach the end. - Skipping the copy. Without
new ArrayList<>(...), editingpodiumorrestwould editrunnersas well.
Key takeaways
subList(i, j)covers indexesitoj - 1; the end is excluded.- Use
list.size()as the end to go to the end of the list. subListis a view onto the original; wrap it innew ArrayList<>(...)for an independent copy.- An end beyond the size, or a start after the end, throws an exception.
