Lesson 19 — Dynamic Data Structures

Collections

Arrays have a fixed size set at creation — you cannot add or remove elements. Java's Collections Framework provides dynamic, type-safe data structures that grow as needed and offer powerful built-in operations.

The Collections Framework

The Java Collections Framework (JCF) is a set of interfaces and classes in java.util that provide standard, reusable implementations of common data structures. The three you will use most are:

  • ArrayList — an ordered, resizable list (like a dynamic array).
  • HashMap — an unordered map from keys to values (like a dictionary).
  • HashSet — an unordered collection of unique values.

Generics

The <T> syntax in ArrayList<String> is a generic type parameter. It tells the compiler what type the collection holds, enabling compile-time type checking and eliminating the need for casting. Always specify the type — using a raw ArrayList without generics is a red flag in professional code.

ArrayList

JAVA — ArrayList
import java.util.ArrayList;
import java.util.Collections;

ArrayList<String> learners = new ArrayList<>();

// Adding elements
learners.add("Thandi");
learners.add("Sipho");
learners.add("Lerato");
learners.add("Bongani");
learners.add(1, "Zanele"); // insert at index 1

System.out.println("Size: " + learners.size());
System.out.println("Get index 2: " + learners.get(2));
System.out.println("Contains Sipho: " + learners.contains("Sipho"));
System.out.println("Index of Lerato: " + learners.indexOf("Lerato"));

// Iterating
for (String name : learners) {
    System.out.println("  " + name);
}

// Removing
learners.remove("Zanele");    // remove by value
learners.remove(0);           // remove by index — careful not to confuse them for Integer lists

// Sorting
Collections.sort(learners);    // alphabetical for Strings
System.out.println("Sorted: " + learners);

// Converting to array when needed
String[] arr = learners.toArray(new String[0]);

ArrayList of Objects

JAVA — ArrayList of objects
ArrayList<Learner> cohort = new ArrayList<>();
cohort.add(new Learner("Thandi",  21, "Java",   74.5));
cohort.add(new Learner("Sipho",   24, "Python", 81.0));
cohort.add(new Learner("Lerato",  20, "SQL",    68.0));
cohort.add(new Learner("Bongani", 22, "C#",     88.5));

// Compute statistics
double total = 0;
int passCount = 0;
for (Learner l : cohort) {
    total += l.getMark();
    if (l.getMark() >= 50) passCount++;
}
System.out.printf("Cohort average: %.1f%% | Pass rate: %d/%d%n",
    total / cohort.size(), passCount, cohort.size());

// Sorting with a custom comparator (lambda — Lesson 17)
cohort.sort((a, b) -> Double.compare(b.getMark(), a.getMark())); // descending by mark
cohort.forEach(l -> System.out.printf("%-12s %.1f%%%n", l.getName(), l.getMark()));

HashMap

A HashMap stores key-value pairs. Looking up a value by key is O(1) on average — extremely fast regardless of how many entries there are. Keys must be unique; values can repeat.

JAVA — HashMap
import java.util.HashMap;
import java.util.Map;

HashMap<String, Double> marks = new HashMap<>();

// Adding entries
marks.put("Thandi",  74.5);
marks.put("Sipho",   81.0);
marks.put("Lerato",  68.0);
marks.put("Bongani", 88.5);

// Reading
System.out.println("Sipho's mark: " + marks.get("Sipho"));           // 81.0
System.out.println("Has Thandi:   " + marks.containsKey("Thandi"));  // true
System.out.println("Has mark 99:  " + marks.containsValue(99.0));    // false

// Safe get with default
double mark = marks.getOrDefault("Zanele", -1.0);
System.out.println("Zanele: " + mark); // -1.0 (not found)

// Iterating key-value pairs
for (Map.Entry<String, Double> entry : marks.entrySet()) {
    System.out.printf("%-10s: %.1f%%%n", entry.getKey(), entry.getValue());
}

// Iterating only keys
for (String name : marks.keySet()) { System.out.println(name); }

// Iterating only values
for (double m : marks.values()) { System.out.println(m); }

// Removing
marks.remove("Bongani");
System.out.println("Size after removal: " + marks.size());

HashSet

A HashSet stores unique elements — duplicates are silently ignored. Use it when you care about membership rather than position or count.

JAVA — HashSet
import java.util.HashSet;

HashSet<String> courses = new HashSet<>();
courses.add("Java");
courses.add("Python");
courses.add("SQL");
courses.add("Java");    // duplicate — silently ignored
courses.add("C#");

System.out.println("Size:         " + courses.size());    // 4 (not 5)
System.out.println("Has Java:     " + courses.contains("Java")); // true
System.out.println("Has Kotlin:   " + courses.contains("Kotlin")); // false

// Set operations (useful for comparisons)
HashSet<String> set1 = new HashSet<>(java.util.Arrays.asList("A","B","C","D"));
HashSet<String> set2 = new HashSet<>(java.util.Arrays.asList("C","D","E","F"));

HashSet<String> union = new HashSet<>(set1);
union.addAll(set2);  // {A, B, C, D, E, F}

HashSet<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2);  // {C, D}

System.out.println("Union: "        + union);
System.out.println("Intersection: " + intersection);

Practice Task

Your Turn

Build a CourseManager using collections: (1) an ArrayList<Learner> to hold all learners, (2) a HashMap<String, ArrayList<Learner>> that maps course names to lists of learners in that course, (3) a method enrol(Learner l, String course) that adds to both, (4) a method getCourseAverage(String course) that returns the average mark for a course, (5) a method topLearner() that finds the highest-scoring learner across all courses. Test with at least 8 learners across 3 courses.

Common Mistakes

  • Removing an element by index vs by value from an ArrayList<Integer>: list.remove(5) removes the element at index 5, but list.remove(Integer.valueOf(5)) removes the first occurrence of the value 5.
  • HashMap does not guarantee order — if you need insertion-order or sorted order, use LinkedHashMap or TreeMap.
  • Using raw types (ArrayList instead of ArrayList<String>) — this compiles with warnings but loses all type safety.
  • Modifying an ArrayList while iterating over it with for-each — use an Iterator or remove by index in a reverse loop.
  • Forgetting that HashSet uses hashCode() and equals() for uniqueness — custom objects need these overridden to work correctly in a HashSet.

Professional Tip

Collections are one of the most-used parts of the Java standard library. The patterns you learn here — putting objects into an ArrayList, grouping with a HashMap, deduplicating with a HashSet — appear in virtually every real Java application from web backends to Android apps.

Mini Quiz

What makes HashMap lookups very efficient?