Java Collections Framework

In the last lesson you learned about Java custom exceptions. Arrays are great, but their size is fixed once created, and real programs add and remove items all the time. Java solves this with the Collections Framework, a set of flexible, resizable containers.

πŸ€” Why not just use arrays?

Say you are writing a to-do app. You do not know how many tasks the user will add. An array makes you guess the size up front. That guess hurts:

  • Their size is fixed. To grow one you build a bigger array and copy every item by hand.
  • They have no built-in helpers. Add in the middle, remove, search, sort. You code all of it yourself.
  • They only do one shape: an ordered list by index. No β€œunique items only” or β€œlook this name up”.

The Collections Framework fixes all of this. The containers grow and shrink on their own. They ship with ready-made add, remove, and search methods. And they offer different shapes for different needs.

Think of an array as a fixed shelf with set slots. A collection is a shopping bag. It stretches to fit whatever you put in.

🧩 What is the Collections Framework?

The Collections Framework is a built-in library of resizable container types, plus the interfaces behind them. It ships with Java. You just import what you need from java.util.

Each container fits a different shape of data:

  • A List is an ordered sequence that allows duplicates.
  • A Set holds only unique items.
  • A Map stores key-to-value pairs.
  • A Queue processes items in the order they arrived.

Let’s meet them one at a time. Then we look at the hierarchy that ties them together.

πŸͺœ The interface hierarchy

The framework is built in two layers:

  • Interfaces describe what a collection can do. An interface is a contract, like β€œa List must support add, remove, get by index”.
  • Classes are the actual working code that fulfills the contract.

At the very top sits Iterable. Below it comes Collection. From Collection branch off List, Set, and Queue. Map sits on its own.

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 things are worth knowing here:

  • Iterable is what lets you loop with a for-each loop. Any type below it works with for (item : collection).
  • Map is not under Collection. A Map holds pairs, not single items. So it lives on its own branch.

You will rarely write Iterable or Collection directly. You work with List, Set, Map, and Queue. The family tree just explains why every List and Set loops the same way.

πŸ“‹ List: an ordered, indexed sequence

A List is like a resizable array:

  • It keeps items in the order you add them.
  • It allows duplicates.
  • You reach items by index.

Think of a music playlist. Order matters, and the same song can appear twice. The main List type is ArrayList, which you will learn next.

Here is a small List that collects tasks. Notice it accepts the same value twice.

import java.util.ArrayList;
ArrayList<String> tasks = new ArrayList<>();
tasks.add("Wake up");
tasks.add("Study Java");
tasks.add("Study Java"); // βœ… duplicates allowed in a List
System.out.println(tasks.get(1)); // reads by index: "Study Java"

Order plus index access plus duplicates. If that describes your data, reach for a List.

🎯 Set: unique items, no duplicates

A Set stores only unique items:

  • Adding something already present is silently ignored. No error.
  • Perfect when duplicates make no sense, like a guest list or unique tags.
  • The main Set type is HashSet.

Here is a Set that quietly drops a repeat.

import java.util.HashSet;
HashSet<String> names = new HashSet<>();
names.add("Alex");
names.add("Riya");
names.add("Alex"); // ❌ ignored, "Alex" is already present
System.out.println(names.size()); // 2, not 3

One thing up front: a HashSet does not keep insertion order. Items sit in whatever order is fastest to look up. Need uniqueness and sorted order? That is TreeSet, which has its own lesson.

πŸ—ΊοΈ Map: key-to-value lookups

A Map stores pairs, a key and its value:

  • You look up a value by its key, and that lookup is fast.
  • Keys are unique. A second value under an existing key replaces the old one.
  • Like a phone contact list: a name maps to a number, and you jump straight to it.
  • The main Map type is HashMap.

Here is a Map from a person’s name to their age.

import java.util.HashMap;
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alex", 25); // key "Alex" maps to value 25
ages.put("Riya", 30);
System.out.println(ages.get("Alex")); // βœ… returns 25, very fast lookup

Note the two types in HashMap<String, Integer>. The first is the key type, the second is the value type. It always reads key first, then value.

🚢 Queue: first in, first out

A Queue holds items in line:

  • Items come out in the order they arrived. First in, first out, or FIFO.
  • Like a line at a ticket counter. First to join is first served, new people join the back.
  • Used for waiting lists and task scheduling.

Here is the idea with LinkedList, which can act as a Queue.

import java.util.LinkedList;
import java.util.Queue;
Queue<String> line = new LinkedList<>();
line.add("Alex"); // joins the back of the line
line.add("Riya");
System.out.println(line.poll()); // βœ… removes and returns "Alex" (first in)

add puts someone at the back. poll takes the next person from the front. That is the FIFO rhythm.

πŸ”€ Generics: telling a collection what it holds

Those angle brackets like ArrayList<String> are called generics. Generics tell a collection exactly what type it may hold, and that keeps your data safe.

import java.util.ArrayList;
ArrayList<String> names = new ArrayList<>();
names.add("Alex");
// names.add(42); // ❌ will not compile, 42 is not a String
String first = names.get(0); // βœ… no casting needed, it is already a String

Two real wins:

  • The compiler stops you adding the wrong type. No number slips into a list of names.
  • You read items out as the right type straight away. No messy casting.
  • Without generics, a collection holds plain Object, so a wrong cast crashes at run time.

For now, always put the element type in the brackets. You will study generics deeply in their own module.

πŸ”’ Autoboxing: storing primitives in collections

Collections only hold objects, not primitives like int or double. So how does List<Integer> work?

The answer is autoboxing:

  • Java wraps a primitive in its matching object type when you put it in.
  • It unwraps it when you take it out.
  • Each primitive has a wrapper: int to Integer, double to Double, boolean to Boolean.

Here is autoboxing happening for you, silently.

import java.util.ArrayList;
ArrayList<Integer> scores = new ArrayList<>();
scores.add(95); // βœ… the int 95 is auto-wrapped into an Integer
scores.add(80);
int total = scores.get(0) + scores.get(1); // βœ… auto-unwrapped back to int
System.out.println(total); // 175

You write 95, but the list stores an Integer. When you read it back, Java unwraps it to an int. The rule: in generics write the wrapper type. So List<Integer>, never List<int>.

🌍 Picking the right collection

Match the job to the shape of your data.

Feature List Set Map
Stores Single items in order Single unique items Key-to-value pairs
Duplicates allowed? Yes No Keys no, values yes
Access by index? Yes No No, access by key
Main type ArrayList HashSet HashMap
Real-life picture Playlist Guest list Phone contacts

The main implementations, each with its own lesson later:

  • ArrayList is the everyday List. Fast to read by index.
  • LinkedList is a List that is also a Queue. Quick to add and remove at the ends.
  • HashSet is the fast, unordered Set.
  • TreeSet is a Set that keeps items sorted. Slower, but ordered.
  • HashMap is the fast, unordered Map, the default for key-value work.
  • TreeMap is a Map that keeps keys sorted.

So Hash versions are fast but unordered. Tree versions are ordered but a bit slower.

πŸ› οΈ A small worked example

Let’s tie a List and a Map together. You are tallying votes. You have a raw list of who each person voted for. You want a final count per candidate.

Read the program once, then we walk through it.

import java.util.ArrayList;
import java.util.HashMap;
public class VoteCounter {
public static void main(String[] args) {
// A List holds every vote in order, duplicates and all
ArrayList<String> votes = new ArrayList<>();
votes.add("Alex");
votes.add("Riya");
votes.add("Alex");
votes.add("Alex");
// A Map will hold each name and its running count
HashMap<String, Integer> counts = new HashMap<>();
for (String name : votes) {
// getOrDefault gives the current count, or 0 if the name is new
int current = counts.getOrDefault(name, 0);
counts.put(name, current + 1); // store the updated count
}
System.out.println(counts);
}
}

What each part does:

  • The votes List is the raw input. β€œAlex” appears three times, which a List allows.
  • The counts Map holds the tally. Key is the name, value is the vote count.
  • The for-each loop visits each vote. This works because List is Iterable.
  • getOrDefault(name, 0) returns the current count, or 0 if the name is new. So no crash on a missing key.
  • put stores the count plus one. The unique-key rule means each name has one entry.

The output looks like this:

Output

{Riya=1, Alex=3}

The List gathered messy input in order. The Map turned it into fast, unique lookups. That teamwork is what the framework is all about. The printed Map order may vary, because a HashMap promises no order.

They use generics

Notice the angle brackets like ArrayList<String> and HashMap<String, Integer>. That is generics, which tell the collection what type it holds. It keeps your data type-safe, so you cannot accidentally put a number into a list of names. You will learn generics in detail in a later module. For now, just put the element type in the brackets.

⚠️ Common Mistakes

A few collections slip-ups to watch for.

  • ❌ Reaching for an array when you need to resize. Use a collection like ArrayList instead.
  • ❌ Using a List when you need uniqueness. A Set enforces no-duplicates for you.
  • ❌ Forgetting the type in the brackets. A raw ArrayList loses type safety. Write ArrayList<String>.
  • ❌ Writing a primitive in generics. List<int> does not compile. Use List<Integer>.
  • ❌ Expecting a HashSet or HashMap to keep order. They do not. Use a Tree version for sorted order.

βœ… Best Practices

Habits for using collections.

  • βœ… Prefer collections over arrays for changing data. They resize on their own and come with helper methods.
  • βœ… Match the collection to the job. List for order, Set for uniqueness, Map for lookups, Queue for FIFO.
  • βœ… Declare against the interface. Write List<String> names = new ArrayList<>();, not ArrayList<String> names = .... This makes it easy to swap the implementation later.
  • βœ… Always declare the element type. List<String>, Map<String, Integer>, and so on, so the compiler can keep you safe.
  • βœ… Use wrapper types in generics. Integer, Double, Boolean. Let autoboxing handle the conversion for you.
  • βœ… Learn each type in turn. The next lessons cover ArrayList, HashSet, HashMap, and more in depth.

🧩 What You’ve Learned

Nicely done. Let’s recap the framework.

  • βœ… The Collections Framework offers flexible, resizable containers beyond fixed arrays, ready to use from java.util.
  • βœ… The hierarchy runs Iterable to Collection, then branches into List, Set, and Queue, with Map on its own branch.
  • βœ… A List keeps an ordered sequence and allows duplicates (ArrayList).
  • βœ… A Set stores only unique items (HashSet), and a Map stores key-to-value pairs for fast lookups (HashMap).
  • βœ… A Queue processes items first-in-first-out, and generics plus autoboxing keep your data type-safe and let you store numbers easily.

Check Your Knowledge

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

  1. 1

    What is the main advantage of collections over arrays?

    Why: Collections grow and shrink automatically and provide methods to add, remove, and search.

  2. 2

    Which collection stores only unique items?

    Why: A Set ignores duplicates, keeping only unique items.

  3. 3

    What does a Map store?

    Why: A Map stores pairs of keys and values, letting you look up a value by its key.

  4. 4

    Why must you write List<Integer> instead of List<int>?

    Why: Collections store objects, not primitives, so you use the wrapper type Integer, and autoboxing converts between int and Integer for you.

πŸš€ What’s Next?

Now that you have the big picture, let’s zoom in on the Collection interface itself. It is the shared contract behind List, Set, and Queue, and understanding it explains why so many collections behave the same way.

Java Collection Interface

Share & Connect