Java Collections Framework
Table of Contents + β
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:
Iterableis what lets you loop with a for-each loop. Any type below it works withfor (item : collection).Mapis not underCollection. 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 ListSystem.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 presentSystem.out.println(names.size()); // 2, not 3One 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 25ages.put("Riya", 30);System.out.println(ages.get("Alex")); // β
returns 25, very fast lookupNote 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 lineline.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 StringString first = names.get(0); // β
no casting needed, it is already a StringTwo 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:
inttoInteger,doubletoDouble,booleantoBoolean.
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 Integerscores.add(80);int total = scores.get(0) + scores.get(1); // β
auto-unwrapped back to intSystem.out.println(total); // 175You 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:
ArrayListis the everyday List. Fast to read by index.LinkedListis a List that is also a Queue. Quick to add and remove at the ends.HashSetis the fast, unordered Set.TreeSetis a Set that keeps items sorted. Slower, but ordered.HashMapis the fast, unordered Map, the default for key-value work.TreeMapis 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
votesList is the raw input. βAlexβ appears three times, which a List allows. - The
countsMap 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, or0if the name is new. So no crash on a missing key.putstores 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
ArrayListinstead. - β Using a List when you need uniqueness. A
Setenforces no-duplicates for you. - β Forgetting the type in the brackets. A raw
ArrayListloses type safety. WriteArrayList<String>. - β Writing a primitive in generics.
List<int>does not compile. UseList<Integer>. - β Expecting a
HashSetorHashMapto keep order. They do not. Use aTreeversion 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<>();, notArrayList<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
What is the main advantage of collections over arrays?
Why: Collections grow and shrink automatically and provide methods to add, remove, and search.
- 2
Which collection stores only unique items?
Why: A Set ignores duplicates, keeping only unique items.
- 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
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.