Skip to main content

07 - Collections Framework: List, Set and Map

Java Collections Framework: ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, Queue and Collections class algorithms.

What do you think about this article?

At a Glance

Java Collections Framework: ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, Queue and Collections class algorithms.

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
Framework Structure
                    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

ArrayList - Dynamic Array
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

LinkedList - Linked List
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

HashSet - Fast but Unordered
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

TreeSet - Naturally Ordered
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

HashMap - Fast Dictionary
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();
    }
}
Iterating over Map
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

TreeMap - Sorted by Key
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.

Queue and PriorityQueue
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
        }
    }
}
Deque - Double-Ended Queue
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.

Collections Utilities
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

Course.java
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);
    }
}
CourseManagement.java
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.

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.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects