Skip to main content
  1. Java Collections Framework/

Collection Interface

7 mins

The Collection interface is the foundation of the Java Collections Framework, defining the basic operations that all collections should support.

Source Code #

View Source on GitHub

Core Interface Definition #

public interface Collection<E> extends Iterable<E> {
    // Query Operations
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    
    // Modification Operations
    boolean add(E e);
    boolean remove(Object o);
    
    // Bulk Operations
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    
    // Comparison and hashing
    boolean equals(Object o);
    int hashCode();
    
    // Default methods (Java 8+)
    default Stream<E> stream() { ... }
    default Stream<E> parallelStream() { ... }
    default boolean removeIf(Predicate<? super E> filter) { ... }
}

Implementation Details #

Query Operations #

size() #

int size();

Returns the number of elements in the collection. Should be O(1) for most implementations.

isEmpty() #

boolean isEmpty();

Returns true if the collection contains no elements. Default implementation:

default boolean isEmpty() {
    return size() == 0;
}

contains(Object o) #

boolean contains(Object o);

Returns true if the collection contains the specified element. Typically O(n) for lists, O(1) for hash-based collections.

iterator() #

Iterator<E> iterator();

Returns an iterator over the elements. The iterator should be fail-fast for most implementations.

toArray() #

Object[] toArray();

Returns an array containing all elements. The runtime type is Object[].

toArray(T[] a) #

<T> T[] toArray(T[] a);

Returns an array containing all elements, using the specified array if it’s large enough.

Modification Operations #

add(E e) #

boolean add(E e);

Adds the specified element to the collection. Returns true if the collection changed.

remove(Object o) #

boolean remove(Object o);

Removes a single instance of the specified element. Returns true if an element was removed.

Bulk Operations #

containsAll(Collection c) #

boolean containsAll(Collection<?> c);

Returns true if the collection contains all elements in the specified collection.

addAll(Collection<? extends E> c) #

boolean addAll(Collection<? extends E> c);

Adds all elements from the specified collection. Returns true if the collection changed.

removeAll(Collection c) #

boolean removeAll(Collection<?> c);

Removes all elements that are contained in the specified collection.

retainAll(Collection c) #

boolean retainAll(Collection<?> c);

Retains only the elements that are contained in the specified collection (intersection).

clear() #

void clear();

Removes all elements from the collection.

Default Methods (Java 8+) #

stream() #

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Returns a sequential Stream with this collection as its source.

parallelStream() #

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

Returns a possibly parallel Stream with this collection as its source.

removeIf(Predicate<? super E> filter) #

default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

Removes all elements that satisfy the given predicate.

Abstract Collection Implementation #

The AbstractCollection class provides skeletal implementations of the Collection interface:

public abstract class AbstractCollection<E> implements Collection<E> {
    protected AbstractCollection() {}
    
    public abstract Iterator<E> iterator();
    public abstract int size();
    
    public boolean isEmpty() {
        return size() == 0;
    }
    
    public boolean contains(Object o) {
        Iterator<E> it = iterator();
        if (o == null) {
            while (it.hasNext())
                if (it.next() == null)
                    return true;
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }
    
    public Object[] toArray() {
        Object[] result = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < result.length; i++)
            result[i] = it.next();
        return result;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                a.getClass().getComponentType(), size);
        
        Iterator<E> it = iterator();
        Object[] result = a;
        for (int i = 0; i < result.length; i++) {
            if (!it.hasNext()) { // fewer elements than expected
                if (a == result) {
                    result = Arrays.copyOf(result, i);
                } else {
                    result[i] = null; // null-terminate
                }
                return (T[])result;
            }
            result[i] = it.next();
        }
        
        if (a.length > size)
            a[size] = null;
        
        return a;
    }
    
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    
    public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o == null) {
            while (it.hasNext()) {
                if (it.next() == null) {
                    it.remove();
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (o.equals(it.next())) {
                    it.remove();
                    return true;
                }
            }
        }
        return false;
    }
    
    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }
    
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
    
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<?> it = iterator();
        while (it.hasNext()) {
            if (c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }
    
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }
    
    public void clear() {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    }
    
    public String toString() {
        Iterator<E> it = iterator();
        if (!it.hasNext())
            return "[]";
        
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (!it.hasNext())
                return sb.append(']').toString();
            sb.append(", ");
        }
    }
}

Key Characteristics #

Optional Operations #

Some operations are optional and may throw UnsupportedOperationException:

  • add(E e) - for immutable collections
  • remove(Object o) - for immutable collections
  • clear() - for immutable collections

Fail-Fast Behavior #

Most implementations provide fail-fast iterators that throw ConcurrentModificationException when the collection is modified during iteration.

Thread Safety #

The Collection interface does not guarantee thread safety. Implementations may or may not be thread-safe:

  • ArrayList, HashSet, HashMap: Not thread-safe
  • Vector, Hashtable: Thread-safe (synchronized methods)
  • CopyOnWriteArrayList, ConcurrentHashMap: Thread-safe with better concurrency

Performance Characteristics #

OperationTypical Complexity
size()O(1)
isEmpty()O(1)
contains(Object)O(n) for lists, O(1) for hash-based
add(E)O(1) amortized for ArrayList, O(1) for LinkedList
remove(Object)O(n) for lists, O(1) for hash-based
iterator()O(1)
toArray()O(n)

Common Implementations #

List Implementations #

  • ArrayList: Resizable array, fast random access
  • LinkedList: Doubly-linked list, fast insertions/removals at ends
  • Vector: Legacy thread-safe ArrayList
  • CopyOnWriteArrayList: Thread-safe list for frequent reads, infrequent writes

Set Implementations #

  • HashSet: Hash table implementation, O(1) operations
  • LinkedHashSet: Hash table + linked list for insertion order
  • TreeSet: Red-black tree for sorted elements
  • EnumSet: Specialized set for enum constants

Queue Implementations #

  • PriorityQueue: Priority heap implementation
  • ArrayDeque: Resizable array deque
  • LinkedList: Also implements Queue interface

Best Practices #

  1. Choose the right implementation: Select based on access patterns and requirements
  2. Use interface types: Program to Collection, List, Set interfaces rather than implementations
  3. Consider thread safety: Use concurrent collections for multi-threaded access
  4. Use bulk operations: addAll(), removeAll() are often more efficient than loops
  5. Leverage default methods: Use stream(), parallelStream(), removeIf() for cleaner code
  6. Be aware of optional operations: Check documentation or use instanceof checks

Common Pitfalls #

  1. ConcurrentModificationException: Modifying collection while iterating
  2. UnsupportedOperationException: Calling optional operations on immutable collections
  3. Performance issues: Choosing wrong implementation for access patterns
  4. Memory leaks: Holding references to large collections unnecessarily
  5. HashCode/equals contracts: Violating these can break hash-based collections

Evolution of the Interface #

Java 1.2 (1998) #

  • Original Collection interface introduced
  • Basic operations: size, isEmpty, contains, iterator, toArray
  • Modification operations: add, remove
  • Bulk operations: containsAll, addAll, removeAll, retainAll, clear

Java 5 (2004) #

  • Generics added: Collection<E>
  • Enhanced for-loop support via Iterable

Java 8 (2014) #

  • Default methods added: stream(), parallelStream(), removeIf()
  • Lambda expressions support

Java 9+ (2017+) #

  • Factory methods: List.of(), Set.of(), Map.of()
  • Collection literals via factory methods

Relationship with Other Interfaces #

Iterable (root)
   ↓
Collection (basic operations)
   ↓
List (positional access)  Set (no duplicates)  Queue (FIFO)
   ↓
Deque (double-ended queue)

When to Use Collection Interface #

Use the Collection interface when:

  1. You need basic collection operations without positional access
  2. You want to write generic code that works with any collection type
  3. You’re implementing a custom collection class
  4. You need to pass collections to methods that don’t care about specific implementation

Use more specific interfaces (List, Set, Queue) when you need their specialized operations.