I am starting a new thread to compare different ways to code the same functionality. In this post, I'll compare two common methods to create a simple list with only one element. Specifically, I'll examine the most commonly used List
implementation constructor and the Collections.singletonList, a straightforward Factory Method for creating an immutable list containing a single element.
Array List
Everytime you initialize an ArrayList
without specifying its initial capacity, it starts with an empty array. When you add the first element, the ArrayList
resizes using a relatively havy algorithm that involves copying the array. Let's take a look at the ArrayList
structure:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
Here's what happens step-by-step:
- An initial empty array
elementData
is created. - When you add the first element, the current size (which is zero) is compared to the length of the array.
- Since it found out that the its size is zero, the array needs to grow to accomodate the new element.
- The
grow
method is called, which calculates the new capacity. - The
newCapacity
is initially increased by half the old capacity, which let us with0 + 0/2 = 0
. - Since
newCapacity
is not sufficient to accomodate the element, it continues. - As it found out that
elementData
is the same initial empty array, it finally returns maximum between the required size (1) and theDEFAULT_CAPACTIY
(10), resulting in an array of size 10.
This resizing process is quite complex when you only need a simple list that will always contain a single element.
That said, let's talk about our alternative!
Collection::singletonList
Method signature:
public static <T> List<T> singletonList(T o)
Description
This method returns an immutable list containing only the specified object. Introducted in Java 1.3, singletonList
has several advantages:
- Inline implementation: You can initialize it with the desired element in a single line.
-
Immutability: Let's take a look at its implementation:
private static class SingletonList<E> extends AbstractList<E> implements RandomAccess, Serializable { private final E element; SingletonList(E obj) { element = obj; } ... }
The
AbstractList
, from whichSingletonList
inherits, defines all mutable methods like this:
public boolean add(E e) { add(size(), e); return true; } public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } public E set(int index, E element) { throw new UnsupportedOperationException(); }
This ensures that it's impossible to chante the list's size or the content of its single element.
Immutability is a highly advantageous feature. While I won't delve into it here, interested developers can learn more from this article.
Memory Allocation: The
SingletonList
class contains only one simple field to accommodate the single element, unlikeArrayList
, which uses an array that, using simpleArrayList
simple constructor, will leave us with an array with size of 10 after element addition.CPU usage: The
SingletonList
constructor accepts the single element as a parameter, requiring no resizing, array copy or maniputalion. This is far more efficient compared to theArrayList
add
method.
Conclusion
In this post, we compared two ways to create a simple list with a single element: using the ArrayList
constructor and the Collection.singletonList
method. While ArrayList
is a flexible and commonly used data structure, it comes with some unnecessary overhead, particularly in terms of memory allocation and CPU usage when adding elements. This overhead includes resizing and copying arrays, which can be redundant for a list that is meant to hold only one element. However, if you need to change this element, ArrayList
is a suitable solution.
On the other hand, Collection.singletonList
offers a more efficient alternative for creating a single-element list. This method is not only more concise and easier to use but also ensures immutability, which can be a significant advantage in many scenarios. It has a minimal memory footprint and requires almost no CPU resources compared to ArrayList
.
In summary, for a simple, immutable list containing only one element, Collection.singletonList
is a superior choice due to its efficiency, simplicity, and immutability. However, if you need to modify the element in the list, the ArrayList
could be an more appropriate choice.
On the next post I'll compare another alternative for a single-element list: List.of
factory method. See you later!
Top comments (0)