Collection Interface
The Collection interface sits at the very top of the Java collections hierarchy. Almost every data structure you use — ArrayList, HashSet, LinkedList, PriorityQueue — traces its roots directly back to this single interface. Understanding it gives you a universal vocabulary for working with any group of objects in Java.

What Is the Collection Interface?
Collection<E> lives in java.util and extends Iterable<E>, which means anything that implements it can be used in a for-each loop. It defines the minimum contract every collection must honor: you can add elements, remove them, check membership, ask for the size, and iterate.
import java.util.Collection;
import java.util.ArrayList;
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("Size: " + fruits.size());
System.out.println("Contains Banana: " + fruits.contains("Banana"));
System.out.println(fruits);
}
}
Output:
Size: 3
Contains Banana: true
[Apple, Banana, Cherry]
Notice that the variable is declared as Collection<String> even though the object is an ArrayList. This is a best practice — program to the interface, not the implementation. You can swap ArrayList for HashSet later without touching any other code.
The Hierarchy at a Glance
Collection sits inside a broader family tree. Here is how the key pieces relate:
Iterable<E>
└── Collection<E>
├── List<E> → ArrayList, LinkedList, Vector
├── Set<E> → HashSet, LinkedHashSet, TreeSet
└── Queue<E> → PriorityQueue, ArrayDeque
Map is not a Collection — it maps keys to values and extends nothing in this hierarchy. You will find it documented separately on the Map Interface page.
Core Methods
Every class that implements Collection must provide these methods:
| Method | Description |
|---|---|
add(E e) | Adds one element; returns true if the collection changed |
addAll(Collection<? extends E> c) | Adds all elements from another collection |
remove(Object o) | Removes a single occurrence of the element |
removeAll(Collection<?> c) | Removes every element that appears in c |
retainAll(Collection<?> c) | Keeps only elements that also appear in c |
contains(Object o) | Returns true if the element is present |
containsAll(Collection<?> c) | Returns true if all elements of c are present |
size() | Returns the number of elements |
isEmpty() | Returns true if size is 0 |
clear() | Removes all elements |
toArray() | Returns an Object[] snapshot |
toArray(T[] a) | Returns a typed array |
iterator() | Returns an Iterator<E> over the elements |
stream() | Returns a sequential Stream<E> (Java 8+) |
parallelStream() | Returns a parallel Stream<E> (Java 8+) |
Working With the Methods
Adding and Removing Elements
import java.util.*;
public class AddRemoveDemo {
public static void main(String[] args) {
Collection<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// Add a single element
numbers.add(6);
// Add all from another collection
numbers.addAll(List.of(7, 8));
// Remove a specific element
numbers.remove(Integer.valueOf(3)); // pass boxed type, not index!
System.out.println(numbers);
}
}
Output:
[1, 2, 4, 5, 6, 7, 8]
Warning: When you call
remove()on aCollection<Integer>, always pass a boxedInteger, not a rawint. If you writenumbers.remove(3), Java compiles it asremove(Object o)when the collection is typed — but on aList, it could match theremove(int index)overload instead, removing the element at index 3. Be explicit withInteger.valueOf(3).
Bulk Operations
import java.util.*;
public class BulkDemo {
public static void main(String[] args) {
Collection<String> a = new ArrayList<>(Arrays.asList("red", "green", "blue"));
Collection<String> b = Arrays.asList("green", "blue", "yellow");
// Intersection — keep only elements in both
Collection<String> intersection = new ArrayList<>(a);
intersection.retainAll(b);
System.out.println("Intersection: " + intersection);
// Difference — remove elements that appear in b
Collection<String> difference = new ArrayList<>(a);
difference.removeAll(b);
System.out.println("Difference: " + difference);
}
}
Output:
Intersection: [green, blue]
Difference: [red]
Iterating Over a Collection
Because Collection extends Iterable, you get three clean ways to iterate:
import java.util.*;
public class IterationDemo {
public static void main(String[] args) {
Collection<String> names = List.of("Alice", "Bob", "Carol");
// 1. for-each (most common)
for (String name : names) {
System.out.println(name);
}
// 2. Iterator (use when you need to remove during iteration)
Collection<String> mutable = new ArrayList<>(names);
Iterator<String> it = mutable.iterator();
while (it.hasNext()) {
if (it.next().startsWith("B")) {
it.remove(); // safe removal during iteration
}
}
System.out.println("After removal: " + mutable);
// 3. Stream (Java 8+)
names.stream()
.filter(n -> n.length() > 3)
.forEach(System.out::println);
}
}
Output:
Alice
Bob
Carol
After removal: [Alice, Carol]
Alice
Carol
Tip: Never call
collection.remove()directly inside a for-each loop — it throwsConcurrentModificationException. Always use theIterator’s ownremove()method, or useremoveIf()(Java 8+).
removeIf — the Cleaner Alternative (Java 8+)
import java.util.*;
public class RemoveIfDemo {
public static void main(String[] args) {
Collection<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
nums.removeIf(n -> n % 2 == 0); // remove all even numbers
System.out.println(nums);
}
}
Output:
[1, 3, 5]
Converting to an Array
import java.util.*;
public class ToArrayDemo {
public static void main(String[] args) {
Collection<String> colors = List.of("red", "green", "blue");
// Typed array (preferred)
String[] arr = colors.toArray(new String[0]);
System.out.println(Arrays.toString(arr));
// Java 11+: cleaner syntax
String[] arr2 = colors.toArray(String[]::new);
System.out.println(Arrays.toString(arr2));
}
}
Output:
[red, green, blue]
[red, green, blue]
Tip: Passing
new String[0]is the idiomatic way since Java 6. The JVM allocates the right-sized array internally — you do not need to pre-size it.
Writing a Method That Accepts Any Collection
Using Collection as a parameter type instead of ArrayList or HashSet makes your methods far more flexible:
import java.util.*;
public class SumDemo {
// Works with ArrayList, HashSet, LinkedList — any Collection of Numbers
public static double sum(Collection<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3))); // 6.0
System.out.println(sum(Set.of(1.5, 2.5, 3.0))); // 7.0
}
}
Under the Hood
How Collection Relates to Iterable
Collection inherits iterator() from Iterable. The for-each loop is pure compiler sugar — for (T x : col) desugars to:
Iterator<T> it = col.iterator();
while (it.hasNext()) {
T x = it.next();
// loop body
}
Every concrete class supplies its own Iterator that knows how to traverse its internal storage — an array index for ArrayList, a node pointer for LinkedList, a bucket walk for HashSet.
Fail-Fast Iterators
Most java.util collections use a fail-fast iterator. They track an internal modCount (a modification counter). Every structural change (add, remove, clear) increments modCount. The iterator saves a snapshot of modCount when created; if it detects a mismatch on the next hasNext() or next() call, it throws ConcurrentModificationException immediately rather than risking silent data corruption.
This is a diagnostic tool, not a thread-safety guarantee. For true concurrent access, use the classes in Concurrent Collections.
AbstractCollection — the Template
Java ships AbstractCollection<E> (an abstract class) that provides default implementations for most Collection methods built on top of just iterator() and size(). Concrete classes like ArrayList override these defaults with far more efficient implementations (e.g., contains in ArrayList is an O(n) array scan, while HashSet overrides it to be O(1)).
Streams Are Lazy
collection.stream() does not immediately process data. It creates a pipeline of operations that only execute when you call a terminal operation (forEach, collect, count, etc.). This means chaining several .filter() and .map() calls costs almost nothing until the terminal step triggers the traversal.
Quick Comparison: Sub-Interfaces of Collection
| Interface | Duplicates | Order Guaranteed | Typical Use |
|---|---|---|---|
List | Yes | Yes (insertion order) | Ordered sequences, index access |
Set | No | Depends on impl | Unique values, membership testing |
Queue | Yes | FIFO / priority | Task queues, scheduling |
Related Topics
- Collections Framework — a bird’s-eye view of the entire framework and when to pick each data structure
- List Interface — ordered, index-accessible collections that allow duplicates
- Set Interface — collections that enforce uniqueness
- Queue & PriorityQueue — FIFO and priority-based element ordering
- Iterator — how to safely traverse and mutate any collection
- Stream API — functional-style bulk operations on any
Collection