ArrayList

Classical data structure that is often fast, until it isn't.


Context

An ArrayList is a resizable array backed by a contiguous block of memory.

This layout gives it two major strengths:

Appending elements is O(1) amortized:

Its main weakness is structural:

ArrayList is fast when writes are append-heavy and indices are stable. It degrades quickly when used like a queue or list of frequent inserts.


Core idea

This is a deliberate design, not a flaw.


Implementation

import java.util.*;

public class ArrayList<T> implements Iterable<T> {
    private int size;
    private T[] elements;
    private int modCount;
    
    @SuppressWarnings("unchecked")
    public ArrayList(int initialCapacity) {
        elements = (T[]) new Object[initialCapacity];
    }
    
    public void append(T element) {
        resizeIfNeeded();
        writeAtIndex(element, size);
    }
    
    public void prepend(T element) {
        resizeIfNeeded();
        System.arraycopy(elements, 0, elements, 1, size);
        writeAtIndex(element, 0);
    }
    
    public void insertAt(T element, int index) {
        assertIndexForInsert(index);
        resizeIfNeeded();
        System.arraycopy(elements, index, elements, index + 1, size - index);
        writeAtIndex(element, index);
    }
    
    public T get(int index) {
        assertIndexForAccess(index);
        return elements[index];
    }
    
    public T removeAt(int index) {
        assertIndexForAccess(index);
        var value = elements[index];
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null;
        modCount++;
        return value;
    }
    
    public T remove(T element) {
        for (var i = 0; i < size; i++) {
            if (elements[i].equals(element)) {
                return removeAt(i);
            }
        }
        
        return null;
    }
    
    public boolean isEmpty() { return size == 0; }
    
    public int size() { return size; }
    
    @Override
    public ListIterator<T> iterator() { return new ArrayListIterator(); }
    
    private void assertIndexForAccess(int index) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
    }
    
    private void assertIndexForInsert(int index) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException();
    }
    
    private void resizeIfNeeded() {
        if (elements.length == size) {
            var capacity = elements.length * 3 / 2 + 1;
            elements = Arrays.copyOf(elements, capacity);
        }
    }
    
    public void writeAtIndex(T element, int index) {
        elements[index] = element;
        size++;
        modCount++;
    }
    
    private final class ArrayListIterator implements ListIterator<T> {
        private final int expectedModCount = modCount;
        private int currentIndex = 0;
        
        @Override
        public boolean hasNext() {
            assertNoConcurrentModification();
            return currentIndex < size;
        }    
    
        @Override
        public boolean hasPrevious() {
            assertNoConcurrentModification();
            return currentIndex > 0;
        }       
        
        @Override
        public T next() {
            if (!hasNext()) throw new NoSuchElementException();
            return elements[currentIndex++];
        }
        
        @Override
        public T previous() {
            if (!hasPrevious()) throw new NoSuchElementException();
            return elements[--currentIndex];
        }
        
        @Override
        public int nextIndex() { return currentIndex; }
        
        @Override
        public int previousIndex() { return currentIndex - 1; }
            
        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void set(T t) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void add(T t) {
            throw new UnsupportedOperationException();
        }    
            
        private void assertNoConcurrentModification() {
            if (expectedModCount != modCount) throw new ConcurrentModificationException();
        }
    }
}