Java Collection Interface

In the last lesson you learned about the Java Collections Framework. Now let’s zoom in on the piece that ties most of them together: the Collection interface. Understand this one interface and you understand the shared language that List, Set, and Queue all speak.

🤔 Why one shared interface matters

Say you write a method that counts items in a group. Without a shared interface, the pain piles up:

  • You write one version for a List, copy it for a Set, copy it again for a Queue.
  • Three near-identical methods, three places for a bug to hide.
  • A method that only accepts ArrayList can never take a HashSet, even though counting fits both.

The Collection interface fixes this. It is the common contract that List, Set, and Queue all sign. So add, remove, size, and contains mean the same thing whatever you hold. Write your method against Collection once, and it works for every collection.

🧩 What is the Collection interface?

The Collection interface, written java.util.Collection, is the root interface for groups of single items:

  • List, Set, and Queue all extend it.
  • It lists the methods a type must provide, without saying how. ArrayList, HashSet, and the rest fill in the code.
  • Map is not a Collection. It holds key-value pairs, so it sits on its own branch.

Because they all share this contract, they share a big set of methods. Learn the methods once and you know every collection.

🪜 The hierarchy: Iterable to Collection to the rest

At the top sits Iterable. Below it comes Collection. From Collection branch off List, Set, and Queue. Map lives on a separate branch.

Here is the shape in plain text.

Iterable
└── Collection
├── List (ordered, duplicates allowed)
├── Set (no duplicates)
└── Queue (first-in-first-out)
Map (key-value pairs, separate branch)

Two points worth knowing:

  • Iterable is the parent of Collection. It is what lets for (String s : myCollection) work on any List, Set, or Queue.
  • Collection adds the real working methods on top: add, remove, size, and the rest. Iterable only promises you can loop.

You rarely create an Iterable or Collection directly. But you often declare a variable as one, and you will see why in a moment.

🛠️ The methods every Collection has

Because List, Set, and Queue all extend Collection, they share these methods. The ones you will use every day:

  • add(item) adds an item. Returns true if the collection changed.
  • remove(item) removes one matching item. Returns true if something was removed.
  • contains(item) returns true if the item is present.
  • size() returns how many items are in the collection.
  • isEmpty() returns true if there are no items.
  • clear() removes everything.
  • addAll(other) adds every item from another collection.
  • removeAll(other) removes every item that is also in the other collection.
  • retainAll(other) keeps only items that are also in the other collection.
  • iterator() gives you an object to walk through items one by one.
  • stream() opens a stream for modern, pipeline-style processing.
  • toArray() copies the items into a plain array.

You get all of this just by holding a Collection reference, even when the real object is an ArrayList.

🎯 Program to the interface

Declare your variable using the interface, not the concrete class. The code below stores an ArrayList, but the variable type is Collection.

import java.util.ArrayList;
import java.util.Collection;
public class Demo {
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("Has banana? " + fruits.contains("banana"));
System.out.println(fruits);
}
}

Line by line:

  • Collection<String> fruits = new ArrayList<>(); declares a Collection but builds an ArrayList. This is programming to the interface.
  • add puts three strings in, one at a time.
  • size() reports how many items are stored.
  • contains("banana") checks for one specific item.

The output looks like this.

Output

Size: 3
Has banana? true
[apple, banana, cherry]

Why declare it as Collection instead of ArrayList? Because tomorrow a HashSet might fit better. You change one word, new ArrayList<>() becomes new HashSet<>(), and the rest of your code keeps working. Your code depends on the contract, not one class.

🔁 Looping and the common methods together

This program adds some scores, checks them, removes one, and loops through the rest. Notice we never mention ArrayList after the first line.

import java.util.ArrayList;
import java.util.Collection;
public class Scores {
public static void main(String[] args) {
Collection<Integer> scores = new ArrayList<>();
scores.add(90);
scores.add(75);
scores.add(60);
System.out.println("Is it empty? " + scores.isEmpty());
System.out.println("Total scores: " + scores.size());
scores.remove(75); // removes the value 75
System.out.println("After removing 75: " + scores);
// Loop works because Collection is Iterable
int sum = 0;
for (int score : scores) {
sum = sum + score;
}
System.out.println("Sum: " + sum);
}
}

What each part does:

  • isEmpty() returns false here, because we added three items.
  • size() reports the count, which is three.
  • remove(75) takes out the value 75 by matching it.
  • The for-each loop walks every remaining item, because Collection extends Iterable.

The output looks like this.

Output

Is it empty? false
Total scores: 3
After removing 75: [90, 60]
Sum: 150

Watch out with Integer and remove

For a collection of Integer, calling remove(75) removes the value 75. There is a separate index-based remove on List, but plain Collection has no index. So on a Collection reference, remove always means “remove this item”, never “remove the item at this position”.

📦 Bulk operations: addAll, removeAll, retainAll

Sometimes you work with whole groups at once. The Collection interface gives you three bulk operations for that. The program below combines and compares two groups of languages.

import java.util.ArrayList;
import java.util.Collection;
import java.util.Arrays;
public class BulkDemo {
public static void main(String[] args) {
Collection<String> known = new ArrayList<>(Arrays.asList("Java", "Python"));
Collection<String> wanted = new ArrayList<>(Arrays.asList("Python", "Go", "Rust"));
// addAll: add everything from another collection
Collection<String> all = new ArrayList<>(known);
all.addAll(wanted);
System.out.println("After addAll: " + all);
// retainAll: keep only items also in wanted (the overlap)
Collection<String> overlap = new ArrayList<>(known);
overlap.retainAll(wanted);
System.out.println("Overlap (retainAll): " + overlap);
// removeAll: drop items that are also in wanted
Collection<String> onlyKnown = new ArrayList<>(known);
onlyKnown.removeAll(wanted);
System.out.println("Only known (removeAll): " + onlyKnown);
}
}

The three operations:

  • addAll(wanted) pours every item from wanted into all. Both groups join. “Python” appears twice because this is a List.
  • retainAll(wanted) keeps only items also in wanted. The overlap. Only “Python” survives.
  • removeAll(wanted) deletes every item that appears in wanted. “Python” is dropped, leaving “Java”.

The output looks like this.

Output

After addAll: [Java, Python, Python, Go, Rust]
Overlap (retainAll): [Python]
Only known (removeAll): [Java]

Think of these as set-style math. addAll is union, retainAll is intersection, removeAll is difference. Each returns true if the collection changed.

🔧 iterator, stream, and toArray

Three more methods each give you a different way to read everything. The program below shows all three on the same group of names.

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class ReadingDemo {
public static void main(String[] args) {
Collection<String> names = new ArrayList<>();
names.add("Alex");
names.add("Riya");
names.add("Sam");
// iterator: walk items one by one
Iterator<String> it = names.iterator();
while (it.hasNext()) {
System.out.println("iterator: " + it.next());
}
// stream: count names longer than 3 letters
long longNames = names.stream()
.filter(name -> name.length() > 3)
.count();
System.out.println("Names longer than 3: " + longNames);
// toArray: copy into a plain array
Object[] arr = names.toArray();
System.out.println("Array length: " + arr.length);
}
}

What each method gives you:

  • iterator() hands back an object that walks the items with hasNext() and next(). This is the engine behind the for-each loop.
  • stream() opens a pipeline. Here filter keeps names longer than three letters, and count returns how many passed.
  • toArray() copies the items into a plain array, useful when an older method expects an array.

The output looks like this.

Output

iterator: Alex
iterator: Riya
iterator: Sam
iterator: Alex and Riya are longer than 3

A quick correction on the output

Reading the code closely, “Alex” and “Riya” have four letters and “Sam” has three. So count returns 2, and the array length is 3. The real run prints Names longer than 3: 2 and Array length: 3 on the last two lines.

You will meet iterator properly in the next lesson. The point here is that all three methods belong to Collection, so every List, Set, and Queue offers them.

⚠️ Common Mistakes

A few Collection slip-ups to watch for.

  • Confusing Collection with Collections. Collection (no s) is the root interface. Collections (with an s) is a utility class of static helpers like Collections.sort(). One is a contract, the other a toolbox.
Collection<String> c = new ArrayList<>(); // ✅ the interface, a type
Collections.sort(myList); // ✅ the utility class, a helper
// Collection.sort(myList); // ❌ no such thing, wrong one
  • Expecting indexed access on a plain Collection. A Collection reference has no get(index). That method lives on List, not on Collection, because Sets and Queues have no index.
Collection<String> c = new ArrayList<>();
c.add("apple");
// String x = c.get(0); // ❌ does not compile, Collection has no get
List<String> list = new ArrayList<>();
String y = list.get(0); // ✅ get exists on List
  • Forgetting Map is not a Collection. You cannot pass a Map where a Collection is expected. If you need its contents as a collection, use map.keySet(), map.values(), or map.entrySet().

  • Assuming remove uses an index. On a Collection, remove removes a matching item, never the item at a position.

✅ Best Practices

Habits that pay off with collections.

  • Program to the interface. Declare the broadest type that fits your needs, like Collection<String> c = new ArrayList<>();. It keeps your code flexible and easy to change later.
  • Pick the right level. Use Collection when you only need add, remove, size, and looping. Step up to List only when you truly need index access.
  • Accept Collection in method parameters. A method that takes a Collection can be called with a List, a Set, or a Queue. That is more reusable than one that demands an ArrayList.
  • Use bulk operations instead of manual loops. addAll, removeAll, and retainAll are clearer and shorter than writing the loop yourself.
  • Keep Collection and Collections straight. Interface versus utility class. Read the final s carefully.

🧩 What You’ve Learned

Nicely done. Let’s recap the Collection interface.

  • Collection is the root interface for List, Set, and Queue, but not Map, which holds pairs on its own branch.
  • ✅ The hierarchy runs Iterable to Collection to List, Set, and Queue, which is why every collection can be looped with a for-each.
  • ✅ Every Collection shares methods like add, remove, contains, size, isEmpty, clear, iterator, stream, and toArray.
  • Bulk operations addAll, removeAll, and retainAll act on whole groups, like union, difference, and intersection.
  • Programming to the interface with Collection<String> c = new ArrayList<>(); keeps your code flexible, and Collection is not the same as the Collections utility class.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Which of these is NOT a sub-interface of Collection?

    Why: Map holds key-value pairs on a separate branch, so it does not extend Collection. List, Set, and Queue all do.

  2. 2

    What does it mean to program to the interface here?

    Why: You declare the variable with the interface type, such as Collection, and build a concrete class like ArrayList, so the implementation is easy to swap later.

  3. 3

    What is the difference between Collection and Collections?

    Why: Collection (no s) is the root interface. Collections (with s) is a utility class with static helpers like sort and reverse.

  4. 4

    What does retainAll do?

    Why: retainAll keeps only the items that also appear in the other collection, which is the intersection of the two.

🚀 What’s Next?

You saw iterator() make a brief appearance above. Next we will study it properly, along with the Iterable interface that sits at the very top of the hierarchy. You will learn how the for-each loop really works under the hood, and how to remove items safely while looping.

Java Iterator and Iterable

Share & Connect