Back to Blog
Java

Java Collections fill: Replacing List Elements

java collections fill: Learn how to use Collections.fill in Java to replace all list elements with a single value, including behavior, pitfalls, and performance.

java collectionsfill methodlist manipulationjava utilarrays fill
Illustration of Java Collections.fill replacing list elements with a single value

The java collections fill operation is provided by the static method Collections.fill(List<? super T> list, T obj). It replaces every element in the specified list with the given object. This is a simple, linear-time utility for resetting or reinitializing a list's contents without changing its size or structure. For example, if you have a list of integers and want to set every element to zero, you can do this in one call.

List<Integer> scores = new ArrayList<>(Arrays.asList(10, 20, 30, 40)); Collections.fill(scores, 0); System.out.println(scores); // [0, 0, 0, 0]

The method operates on the List interface, so it works with any implementation such as ArrayList, LinkedList, or a custom list. It does not return a new list; it modifies the list in place. The type parameter T is the element type, and the list is declared as List<? super T> to allow filling a list of a supertype with a subtype value, which is a common generics pattern.

How Collections.fill Works

The implementation of Collections.fill is straightforward: it iterates over the list using a ListIterator and calls set on each element. The signature is:

public static <T> void fill(List<? super T> list, T obj)

The method requires that the list supports set operations. For unmodifiable lists, such as those returned by Collections.unmodifiableList, it throws an UnsupportedOperationException. It also throws a NullPointerException if the list is null. The value obj can be null; in that case, every element becomes null. This is allowed and often used to clear a list of references.

Using Collections.fill with ArrayList and LinkedList

Because fill works on the List interface, it behaves consistently across different implementations. With an ArrayList, the set operation is direct array indexing, so the fill is fast and predictable. With a LinkedList, set requires traversing to the index, making the fill operation O(n^2) in the worst case if the list is large. However, Collections.fill internally uses a ListIterator, which for a LinkedList moves sequentially, so the overall time is still O(n) because the iterator advances one step per element. This is a subtle but important detail: the method is implemented to avoid repeated index lookups.

List<String> names = new LinkedList<>(Arrays.asList("Alice", "Bob", "Carol")); Collections.fill(names, "Unknown"); System.out.println(names); // [Unknown, Unknown, Unknown]

In practice, the performance difference between ArrayList and LinkedList for fill is negligible for typical list sizes, but it becomes relevant when the list is very large and the implementation has poor random-access characteristics. The ListIterator approach ensures a single pass regardless of the underlying structure.

What Collections.fill Does Not Do

It is important to understand that Collections.fill does not add elements to the list. If the list is empty, calling fill has no effect because there are no elements to replace. It does not resize the list or insert new elements. To add a repeated value to a list that does not yet have the desired size, you need to use a loop or Collections.nCopies combined with a constructor. For example, to create a list of five null values, you cannot use fill on an empty list; instead, you would write:

List<String> list = new ArrayList<>(Collections.nCopies(5, null));

The fill method only modifies existing elements. This is a common misconception, so it is worth verifying the list's size before relying on fill to initialize it.

Performance and Memory Behavior

The time complexity of Collections.fill is O(n), where n is the number of elements in the list. It performs one set operation per element, and the underlying list iterator advances linearly. No additional memory is allocated beyond a single iterator object, so the memory overhead is constant. This makes fill an efficient way to reset a list when you need to reuse the same list object rather than creating a new one.

Creating a new list with repeated values, such as using Collections.nCopies and passing it to a constructor, also runs in O(n) time but allocates a new list and may copy references. If you need to preserve the original list identity—for example, because other objects hold a reference to it—fill is the better choice. If you can discard the old list, building a new one might be clearer.

Common Pitfalls and Edge Cases

Several edge cases can cause unexpected behavior or exceptions:

  • Unmodifiable lists: Collections.fill throws UnsupportedOperationException if the list does not support set. This includes lists created with Collections.unmodifiableList or List.of (Java 9+).
  • Fixed-size lists: Arrays.asList returns a fixed-size list backed by an array. It supports set, so fill works, but you cannot add or remove elements. fill only replaces, so it is safe.
  • Null values: Passing null as the value sets every element to null. This is often used to release references, but be aware that it may cause NullPointerException later if the list is used without null checks.
  • Concurrent modification: If the list is modified by another thread while fill is running, the behavior is undefined. Collections.fill is not thread-safe; you must synchronize externally if the list is shared.
  • Empty list: As mentioned, fill does nothing on an empty list. It does not throw an error; it simply iterates zero times.

These edge cases are not obscure; they are common in production code, especially when dealing with immutable collections or shared data structures.

Alternatives to Collections.fill

For arrays, the java.util.Arrays class provides a similar method: Arrays.fill. It works on arrays of primitive types and objects. For example:

int[] arr = new int[5]; Arrays.fill(arr, 42);

For lists, you can also use a simple loop:

for (int i = 0; i < list.size(); i++) { list.set(i, value); }

This is functionally equivalent to Collections.fill, but the loop gives you more control, such as conditionally setting values based on index. Java streams offer another way, but they are not designed for in-place modification; you would typically create a new list:

List<Integer> newList = list.stream().map(x -> 0).collect(Collectors.toList());

This is less efficient and changes the list identity, so it is not a direct replacement.

Choosing Between Collections.fill and Manual Loops

Use Collections.fill when you need to set every element to the same value and the list is mutable and supports set. It is concise, self-documenting, and avoids off-by-one errors. Use a manual loop when you need to apply different values based on the index or when you need to perform additional logic during the iteration. For example, if you want to fill a list with a sequence of numbers, a loop is necessary:

for (int i = 0; i < list.size(); i++) { list.set(i, i * 10); }

For primitive arrays, Arrays.fill is the direct equivalent and should be preferred over a loop for readability. For immutable lists, neither fill nor a loop works; you must create a new list. The decision ultimately comes down to whether the operation is a uniform replacement (use fill) or a computed replacement (use a loop).

When performance is critical, Collections.fill is as efficient as a manual loop because it uses the same set operation internally. The only overhead is the iterator creation, which is negligible. In concurrent scenarios, you must synchronize the list regardless of which approach you choose. In practice, Collections.fill is the clearest way to express the intent of resetting a list, and it is the idiomatic Java approach for this operation.

java collections fill: Practical Usage and Code Examples | RYUSLOG DEV