The Collections Framework
Java's Collections Framework is a unified architecture for representing and manipulating groups of objects. It provides interfaces, implementations, and algorithms to manage data efficiently.
What You Will Learn
- Collection interface hierarchy
- List: ArrayList and LinkedList
- Set: HashSet, TreeSet and LinkedHashSet
- Map: HashMap, TreeMap and LinkedHashMap
- Queue and Deque
- Collections utility algorithms
Collections Hierarchy
Main Interfaces
| Interface | Description | Implementations |
|---|---|---|
| Collection | Root interface | - |
| List | Ordered sequence with duplicates | ArrayList, LinkedList |
| Set | Unique elements | HashSet, TreeSet |
| Queue | FIFO queue | LinkedList, PriorityQueue |
| Map | Key-value pairs | HashMap, TreeMap |
Iterable
|
Collection
/ | \
List Set Queue
| | |
ArrayList HashSet LinkedList
LinkedList TreeSet PriorityQueue
Map (separate)
|
HashMap TreeMap
List - Ordered Lists
List maintains elements in insertion order
and allows duplicates. It is indexed (position-based access).
ArrayList
import java.util.ArrayList;
import java.util.List;
public class StudentRegistry {
public static void main(String[] args) {
// Create list
List<String> students = new ArrayList<>();
// Add elements
students.add("John Smith");
students.add("Laura White");
students.add("Joseph Green");
students.add(1, "Anna Black"); // Insert at position 1
// Access by index
String first = students.get(0); // "John Smith"
String second = students.get(1); // "Anna Black"
// Size
int numStudents = students.size(); // 4
// Modify element
students.set(0, "Mark Smith");
// Search
boolean present = students.contains("Laura White"); // true
int position = students.indexOf("Joseph Green"); // 3
// Remove
students.remove("Anna Black"); // by value
students.remove(0); // by index
// Iteration
for (String student : students) {
System.out.println(student);
}
// Iteration with index
for (int i = 0; i < students.size(); i++) {
System.out.println((i + 1) + ". " + students.get(i));
}
}
}
LinkedList
import java.util.LinkedList;
import java.util.List;
public class ExamQueue {
public static void main(String[] args) {
LinkedList<String> queue = new LinkedList<>();
// LinkedList-specific operations
queue.addFirst("Urgent student"); // At head
queue.addLast("Normal student"); // At tail
queue.add("Another student"); // At tail
// Access first/last
String first = queue.getFirst();
String last = queue.getLast();
String firstPeek = queue.peekFirst(); // Doesn't remove
// Remove first/last
String removedFirst = queue.removeFirst();
String removedLast = queue.removeLast();
// Use as Stack (LIFO)
queue.push("New on top");
String popped = queue.pop();
// Use as Queue (FIFO)
queue.offer("New at tail");
String served = queue.poll();
}
}
ArrayList vs LinkedList
| Operation | ArrayList | LinkedList |
|---|---|---|
| get(index) | O(1) - Fast | O(n) - Slow |
| add(element) | O(1) amortized | O(1) |
| add(0, element) | O(n) - Slow | O(1) - Fast |
| remove(0) | O(n) - Slow | O(1) - Fast |
| Memory | Less overhead | More overhead (nodes) |
Tip: Use ArrayList as default, LinkedList only for frequent insertions/removals at head.
Set - Unique Sets
Set does not allow duplicates. It's ideal when you need
to guarantee element uniqueness.
HashSet
import java.util.HashSet;
import java.util.Set;
public class UniqueCourses {
public static void main(String[] args) {
Set<String> attendedCourses = new HashSet<>();
// Add elements
attendedCourses.add("Programming");
attendedCourses.add("Database");
attendedCourses.add("Networks");
attendedCourses.add("Programming"); // Duplicate ignored!
System.out.println(attendedCourses.size()); // 3
// Check presence
if (attendedCourses.contains("Database")) {
System.out.println("Course already attended");
}
// Remove
attendedCourses.remove("Networks");
// Iteration (order NOT guaranteed)
for (String course : attendedCourses) {
System.out.println(course);
}
// Set operations
Set<String> newCourses = new HashSet<>();
newCourses.add("AI");
newCourses.add("Database");
// Union
Set<String> all = new HashSet<>(attendedCourses);
all.addAll(newCourses);
// Intersection
Set<String> common = new HashSet<>(attendedCourses);
common.retainAll(newCourses);
// Difference
Set<String> onlyOld = new HashSet<>(attendedCourses);
onlyOld.removeAll(newCourses);
}
}
TreeSet and LinkedHashSet
import java.util.TreeSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class SetTypes {
public static void main(String[] args) {
// TreeSet: automatically ordered
Set<Integer> grades = new TreeSet<>();
grades.add(85);
grades.add(92);
grades.add(78);
grades.add(88);
System.out.println(grades); // [78, 85, 88, 92] - sorted!
// TreeSet specific methods
TreeSet<Integer> gradesTree = new TreeSet<>(grades);
Integer minimum = gradesTree.first(); // 78
Integer maximum = gradesTree.last(); // 92
Integer lower = gradesTree.lower(85); // 78 (strictly <)
Integer higher = gradesTree.higher(85); // 88 (strictly >)
// Subset
Set<Integer> mid = gradesTree.subSet(80, 90); // [85, 88]
// LinkedHashSet: maintains insertion order
Set<String> registrationOrder = new LinkedHashSet<>();
registrationOrder.add("John");
registrationOrder.add("Anna");
registrationOrder.add("Luigi");
// Iterates in insertion order
for (String name : registrationOrder) {
System.out.println(name); // John, Anna, Luigi
}
}
}
Set Comparison
| Type | Order | Performance | Typical use |
|---|---|---|---|
| HashSet | None | O(1) | Maximum speed |
| TreeSet | Natural/Comparator | O(log n) | Sorted elements |
| LinkedHashSet | Insertion | O(1) | Order preserved |
Map - Key-Value Pairs
Map associates unique keys to values. It's the most
used data structure for fast lookups and dictionaries.
HashMap
import java.util.HashMap;
import java.util.Map;
public class GradeRegistry {
public static void main(String[] args) {
// Map student -> grade
Map<String, Integer> grades = new HashMap<>();
// Insert
grades.put("John Smith", 85);
grades.put("Laura White", 92);
grades.put("Joseph Green", 78);
// Access
Integer johnGrade = grades.get("John Smith"); // 85
Integer missingGrade = grades.get("Unknown"); // null
// getOrDefault: avoids null
Integer safeGrade = grades.getOrDefault("Unknown", 0);
// Check presence
if (grades.containsKey("Laura White")) {
System.out.println("Laura is registered");
}
if (grades.containsValue(92)) {
System.out.println("Someone got 92!");
}
// Update
grades.put("John Smith", 88); // Overwrites
// putIfAbsent: inserts only if absent
grades.putIfAbsent("John Smith", 70); // Doesn't change (already present)
// Remove
grades.remove("Joseph Green");
grades.remove("Laura White", 92); // Removes only if value matches
// Size
int numStudents = grades.size();
boolean empty = grades.isEmpty();
}
}
Map<String, Integer> grades = new HashMap<>();
grades.put("John", 85);
grades.put("Laura", 92);
grades.put("Joseph", 78);
// Iterate over keys
for (String student : grades.keySet()) {
System.out.println(student);
}
// Iterate over values
for (Integer grade : grades.values()) {
System.out.println(grade);
}
// Iterate over entries (Entry) - more efficient
for (Map.Entry<String, Integer> entry : grades.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// forEach with lambda (Java 8+)
grades.forEach((student, grade) ->
System.out.println(student + " got " + grade)
);
TreeMap and LinkedHashMap
import java.util.TreeMap;
import java.util.LinkedHashMap;
public class MapTypes {
public static void main(String[] args) {
// TreeMap: sorted keys
TreeMap<String, Integer> alphabetic = new TreeMap<>();
alphabetic.put("Zeta", 1);
alphabetic.put("Alpha", 2);
alphabetic.put("Beta", 3);
// Iterates in alphabetical order
for (String key : alphabetic.keySet()) {
System.out.println(key); // Alpha, Beta, Zeta
}
// Navigation methods
String first = alphabetic.firstKey(); // "Alpha"
String last = alphabetic.lastKey(); // "Zeta"
String lower = alphabetic.lowerKey("Beta"); // "Alpha"
// LinkedHashMap: insertion order
LinkedHashMap<String, Integer> ordered = new LinkedHashMap<>();
ordered.put("First", 1);
ordered.put("Second", 2);
ordered.put("Third", 3);
// Maintains insertion order
for (String key : ordered.keySet()) {
System.out.println(key); // First, Second, Third
}
}
}
Queue and Deque
Queue represents a FIFO (First-In-First-Out) queue.
Deque is a double-ended queue.
import java.util.Queue;
import java.util.LinkedList;
import java.util.PriorityQueue;
public class QueueManagement {
public static void main(String[] args) {
// FIFO queue with LinkedList
Queue<String> serviceQueue = new LinkedList<>();
// offer: adds at tail
serviceQueue.offer("Customer 1");
serviceQueue.offer("Customer 2");
serviceQueue.offer("Customer 3");
// peek: looks at first without removing
String next = serviceQueue.peek(); // "Customer 1"
// poll: removes and returns first
String served = serviceQueue.poll(); // "Customer 1"
System.out.println("Served: " + served);
// PriorityQueue: priority order
Queue<Integer> priority = new PriorityQueue<>();
priority.offer(30);
priority.offer(10);
priority.offer(20);
// Extracts in priority order (natural = ascending)
while (!priority.isEmpty()) {
System.out.println(priority.poll()); // 10, 20, 30
}
// PriorityQueue with custom order
Queue<String> byLength = new PriorityQueue<>(
(a, b) -> a.length() - b.length()
);
byLength.offer("longer");
byLength.offer("xx");
byLength.offer("medium");
while (!byLength.isEmpty()) {
System.out.println(byLength.poll()); // xx, longer, medium
}
}
}
import java.util.Deque;
import java.util.ArrayDeque;
public class DoubleQueue {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
// Head operations
deque.addFirst("A");
deque.offerFirst("B");
// Tail operations
deque.addLast("C");
deque.offerLast("D");
// State: [B, A, C, D]
// Remove from head
String first = deque.removeFirst(); // "B"
String firstPoll = deque.pollFirst(); // "A"
// Remove from tail
String last = deque.removeLast(); // "D"
String lastPoll = deque.pollLast(); // "C"
// Use as Stack (LIFO)
Deque<String> stack = new ArrayDeque<>();
stack.push("First"); // On top
stack.push("Second");
stack.push("Third");
while (!stack.isEmpty()) {
System.out.println(stack.pop()); // Third, Second, First
}
}
}
Collections Class Algorithms
The Collections class provides static methods
for common operations on collections.
import java.util.*;
public class CollectionsUtility {
public static void main(String[] args) {
List<Integer> grades = new ArrayList<>(
Arrays.asList(78, 92, 85, 75, 88, 92)
);
// Sorting
Collections.sort(grades);
System.out.println(grades); // [75, 78, 85, 88, 92, 92]
// Reverse sort
Collections.sort(grades, Collections.reverseOrder());
System.out.println(grades); // [92, 92, 88, 85, 78, 75]
// Binary search (list must be sorted!)
Collections.sort(grades);
int index = Collections.binarySearch(grades, 88);
// Min and Max
int minimum = Collections.min(grades); // 75
int maximum = Collections.max(grades); // 92
// Frequency
int count92 = Collections.frequency(grades, 92); // 2
// Shuffle
Collections.shuffle(grades);
// Reverse
Collections.reverse(grades);
// Fill
Collections.fill(grades, 0); // All to 0
// Swap elements
Collections.swap(grades, 0, 1);
// Rotation
List<String> list = new ArrayList<>(
Arrays.asList("A", "B", "C", "D")
);
Collections.rotate(list, 1); // [D, A, B, C]
Collections.rotate(list, -1); // [A, B, C, D]
// Immutable lists
List<String> immutable = Collections.unmodifiableList(list);
// immutable.add("X"); // UnsupportedOperationException!
// Empty immutable list
List<String> empty = Collections.emptyList();
// Singleton (single element list)
List<String> single = Collections.singletonList("Only");
}
}
Complete Example: Course Management System
package school;
import java.util.*;
public class Course {
private String name;
private String teacher;
private int maxStudents;
private Set<String> enrolledStudents;
private Map<String, List<Integer>> studentGrades;
public Course(String name, String teacher, int maxStudents) {
this.name = name;
this.teacher = teacher;
this.maxStudents = maxStudents;
this.enrolledStudents = new LinkedHashSet<>();
this.studentGrades = new HashMap<>();
}
public boolean enroll(String student) {
if (enrolledStudents.size() >= maxStudents) {
System.out.println("Course is full!");
return false;
}
if (enrolledStudents.contains(student)) {
System.out.println(student + " already enrolled!");
return false;
}
enrolledStudents.add(student);
studentGrades.put(student, new ArrayList<>());
return true;
}
public void recordGrade(String student, int grade) {
if (!enrolledStudents.contains(student)) {
System.out.println(student + " not enrolled!");
return;
}
if (grade < 60 || grade > 100) {
System.out.println("Invalid grade");
return;
}
studentGrades.get(student).add(grade);
}
public double calculateAverage(String student) {
List<Integer> grades = studentGrades.get(student);
if (grades == null || grades.isEmpty()) {
return 0;
}
return grades.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
}
public Map<String, Double> getStudentRanking() {
Map<String, Double> ranking = new TreeMap<>();
for (String student : enrolledStudents) {
ranking.put(student, calculateAverage(student));
}
return ranking;
}
public List<String> getStudentsSortedByAverage() {
List<String> sorted = new ArrayList<>(enrolledStudents);
sorted.sort((s1, s2) ->
Double.compare(calculateAverage(s2), calculateAverage(s1))
);
return sorted;
}
public void printSummary() {
System.out.println("╔══════════════════════════════════════╗");
System.out.println("║ Course: " + name);
System.out.println("║ Teacher: " + teacher);
System.out.println("║ Enrolled: " + enrolledStudents.size() + "/" + maxStudents);
System.out.println("╠══════════════════════════════════════╣");
for (String student : getStudentsSortedByAverage()) {
List<Integer> grades = studentGrades.get(student);
double average = calculateAverage(student);
System.out.printf("║ %-20s Average: %.2f%n", student, average);
System.out.println("║ Grades: " + grades);
}
System.out.println("╚══════════════════════════════════════╝");
}
// Getters
public String getName() { return name; }
public Set<String> getEnrolledStudents() {
return Collections.unmodifiableSet(enrolledStudents);
}
}
package school;
import java.util.*;
public class CourseManagement {
private Map<String, Course> courses;
private Map<String, Set<String>> studentEnrollments;
public CourseManagement() {
this.courses = new HashMap<>();
this.studentEnrollments = new HashMap<>();
}
public void addCourse(Course course) {
courses.put(course.getName(), course);
}
public boolean enrollStudent(String student, String courseName) {
Course course = courses.get(courseName);
if (course == null) {
System.out.println("Course not found: " + courseName);
return false;
}
if (course.enroll(student)) {
studentEnrollments
.computeIfAbsent(student, k -> new HashSet<>())
.add(courseName);
return true;
}
return false;
}
public Set<String> getStudentCourses(String student) {
return studentEnrollments.getOrDefault(student, Collections.emptySet());
}
public void printStatistics() {
System.out.println("\n=== GLOBAL STATISTICS ===");
System.out.println("Total courses: " + courses.size());
int totalEnrollments = studentEnrollments.values()
.stream()
.mapToInt(Set::size)
.sum();
System.out.println("Total enrollments: " + totalEnrollments);
// Student with most courses
String mostActive = studentEnrollments.entrySet()
.stream()
.max(Comparator.comparingInt(e -> e.getValue().size()))
.map(Map.Entry::getKey)
.orElse("None");
System.out.println("Most active student: " + mostActive);
}
public static void main(String[] args) {
CourseManagement management = new CourseManagement();
// Create courses
management.addCourse(new Course("Programming", "Prof. White", 30));
management.addCourse(new Course("Database", "Prof. Smith", 25));
management.addCourse(new Course("Networks", "Prof. Green", 20));
// Enroll students
management.enrollStudent("John", "Programming");
management.enrollStudent("John", "Database");
management.enrollStudent("Laura", "Programming");
management.enrollStudent("Laura", "Networks");
management.enrollStudent("Joseph", "Database");
// Record grades
Course prog = management.courses.get("Programming");
prog.recordGrade("John", 85);
prog.recordGrade("John", 92);
prog.recordGrade("Laura", 88);
Course db = management.courses.get("Database");
db.recordGrade("John", 78);
db.recordGrade("Joseph", 92);
// Print summary
prog.printSummary();
db.printSummary();
// Statistics
management.printStatistics();
System.out.println("\nJohn's courses: " + management.getStudentCourses("John"));
}
}
Conclusion
The Collections Framework is fundamental for every Java developer. The right collection choice depends on requirements:
Key Points to Remember
- ArrayList: Fast index access, default for lists
- LinkedList: Fast insertions/removals at head
- HashSet: Uniqueness with maximum speed
- TreeSet: Uniqueness with ordering
- HashMap: Fast key-value lookup
- TreeMap: Sorted keys
- PriorityQueue: Priority-based extraction
- ArrayDeque: Efficient Stack/Queue
In the next article we'll cover error handling and exceptions: try-catch, throw, checked and unchecked exceptions.







