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.
Related deep dive: 08 - Gestione degli Errori ed Eccezioni.
Related deep dive: 09 - Input/Output e NIO.2.
Frequently asked questions
What is the primary purpose of Java's Collections Framework?
The primary purpose of Java's Collections Framework is to provide a unified architecture for representing and manipulating groups of objects.
What is the main difference between a List and a Set in the Collections Framework?
A List allows duplicates, while a Set does not allow duplicates, making it ideal when you need to guarantee element uniqueness.
Why would you choose LinkedList over ArrayList?
You should use LinkedList only for frequent insertions or removals at the head of the collection, as ArrayList is generally more efficient in other scenarios.
What type of data structure is a Map in the Collections Framework?
A Map associates unique keys to values and is ideal for fast lookups and dictionaries.
Commenti
Caricamento commenti...
Sign in to comment