Learn Java in 10 DaysDay 9: Collections and Generics

Day 9: Collections and Generics

What You'll Learn Today

  • List (ArrayList, LinkedList)
  • Set (HashSet, TreeSet)
  • Map (HashMap, TreeMap)
  • Generics
  • Stream API basics

The Collections Framework

Java's Collections Framework provides a unified API for working with groups of data.

flowchart TB
    Collection["<<interface>><br>Collection"]
    List["<<interface>><br>List"]
    Set["<<interface>><br>Set"]
    Map["<<interface>><br>Map"]
    ArrayList["ArrayList"]
    LinkedList["LinkedList"]
    HashSet["HashSet"]
    TreeSet["TreeSet"]
    HashMap["HashMap"]
    TreeMap["TreeMap"]
    Collection --> List
    Collection --> Set
    List --> ArrayList
    List --> LinkedList
    Set --> HashSet
    Set --> TreeSet
    Map --> HashMap
    Map --> TreeMap
    style Collection fill:#3b82f6,color:#fff
    style List fill:#22c55e,color:#fff
    style Set fill:#f59e0b,color:#fff
    style Map fill:#8b5cf6,color:#fff

List

An ordered collection that allows duplicates.

ArrayList

import java.util.ArrayList;
import java.util.List;

// Create
List<String> fruits = new ArrayList<>();

// Add
fruits.add("Apple");
fruits.add("Orange");
fruits.add("Grape");
fruits.add("Apple"); // duplicates allowed

// Access
fruits.get(0)            // "Apple"
fruits.size()            // 4
fruits.contains("Orange") // true

// Modify and remove
fruits.set(1, "Banana");   // replace at index 1
fruits.remove("Grape");     // remove by value
fruits.remove(0);           // remove by index

// Loop
for (String fruit : fruits) {
    System.out.println(fruit);
}

Immutable Lists (Java 9+)

// Unmodifiable list
List<String> colors = List.of("Red", "Blue", "Green");
// colors.add("Yellow"); // UnsupportedOperationException

Key Methods

Method Description
add(e) Append to end
add(i, e) Insert at index
get(i) Get element
set(i, e) Replace element
remove(i) / remove(e) Remove element
size() Number of elements
contains(e) Check membership
indexOf(e) Find index
isEmpty() Check if empty
sort(comparator) Sort the list

ArrayList vs LinkedList

Feature ArrayList LinkedList
Random access Fast O(1) Slow O(n)
Insert/delete at head Slow O(n) Fast O(1)
Memory efficiency Good Higher overhead
Recommendation Use by default Only for special cases

Set

A collection with no duplicates and no guaranteed order.

import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

// HashSet (unordered)
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("Java");    // duplicate ignored
System.out.println(hashSet.size()); // 2

// TreeSet (sorted)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
System.out.println(treeSet); // [10, 20, 30]

// Immutable Set (Java 9+)
Set<String> immutable = Set.of("A", "B", "C");

Set Operations

Set<Integer> a = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> b = new HashSet<>(Set.of(3, 4, 5, 6));

// Union
Set<Integer> union = new HashSet<>(a);
union.addAll(b);          // {1, 2, 3, 4, 5, 6}

// Intersection
Set<Integer> intersection = new HashSet<>(a);
intersection.retainAll(b); // {3, 4}

// Difference
Set<Integer> diff = new HashSet<>(a);
diff.removeAll(b);         // {1, 2}

Map

A collection that stores key-value pairs.

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

// HashMap
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 92);
scores.put("Charlie", 78);

// Access
scores.get("Alice")              // 85
scores.getOrDefault("Diana", 0)  // 0 (default when key is missing)
scores.containsKey("Bob")        // true
scores.size()                    // 3

// Update and remove
scores.put("Alice", 90);         // overwrite
scores.remove("Charlie");        // remove

// Loop
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.printf("%s: %d points%n", entry.getKey(), entry.getValue());
}

// Keys only / Values only
scores.keySet()   // [Alice, Bob]
scores.values()   // [90, 92]

Immutable Map (Java 9+)

Map<String, Integer> map = Map.of(
    "one", 1,
    "two", 2,
    "three", 3
);

Useful Methods

// putIfAbsent: add only if key doesn't exist
scores.putIfAbsent("Diana", 88);

// computeIfAbsent: compute and add if key doesn't exist
Map<String, List<String>> groups = new HashMap<>();
groups.computeIfAbsent("A", k -> new ArrayList<>()).add("Alice");

// merge: merge values
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
}

Generics

Generics let you parameterize types, enabling type-safe code.

// Without generics (unsafe)
List rawList = new ArrayList();
rawList.add("Hello");
rawList.add(123);
String s = (String) rawList.get(1); // ClassCastException!

// With generics (safe)
List<String> safeList = new ArrayList<>();
safeList.add("Hello");
// safeList.add(123); // compile error
String s = safeList.get(0); // no cast needed

Generic Classes

class Pair<A, B> {
    private final A first;
    private final B second;

    Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }

    A getFirst() { return first; }
    B getSecond() { return second; }

    @Override
    public String toString() {
        return "(" + first + ", " + second + ")";
    }
}

// Usage
Pair<String, Integer> nameAge = new Pair<>("Alice", 25);
System.out.println(nameAge); // (Alice, 25)

Generic Methods

static <T> T getFirst(List<T> list) {
    if (list.isEmpty()) {
        throw new IllegalArgumentException("List is empty");
    }
    return list.get(0);
}

// Usage
String first = getFirst(List.of("A", "B", "C")); // "A"
Integer num = getFirst(List.of(1, 2, 3));          // 1

Bounded Type Parameters

// T must implement Comparable
static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

max(3, 7)              // 7
max("apple", "banana") // "banana"

Stream API Basics

The Stream API lets you process collections declaratively.

import java.util.List;
import java.util.stream.Collectors;

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Filter + transform + collect
List<Integer> evenDoubled = numbers.stream()
    .filter(n -> n % 2 == 0)        // keep even numbers
    .map(n -> n * 2)                 // double them
    .toList();                       // collect to list
// [4, 8, 12, 16, 20]

// Sum
int sum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum(); // 55

// Join strings
List<String> names = List.of("Alice", "Bob", "Charlie");
String joined = names.stream()
    .collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"
flowchart LR
    Source["Source<br>[1,2,3,4,5]"]
    Filter["filter<br>Even only"]
    Map["map<br>Double"]
    Collect["toList<br>Collect"]
    Source --> Filter --> Map --> Collect
    style Source fill:#3b82f6,color:#fff
    style Filter fill:#f59e0b,color:#fff
    style Map fill:#22c55e,color:#fff
    style Collect fill:#8b5cf6,color:#fff

Common Stream Operations

Operation Type Description
filter() Intermediate Filter by condition
map() Intermediate Transform elements
sorted() Intermediate Sort
distinct() Intermediate Remove duplicates
limit() Intermediate Limit count
toList() Terminal Collect to list
forEach() Terminal Process each element
count() Terminal Count elements
reduce() Terminal Aggregate

Hands-On: Student Management System

import java.util.*;
import java.util.stream.Collectors;

record Student(String name, int score, String subject) {}

public class StudentManager {
    public static void main(String[] args) {
        List<Student> students = List.of(
            new Student("Alice", 85, "Math"),
            new Student("Bob", 92, "English"),
            new Student("Charlie", 78, "Math"),
            new Student("Diana", 95, "English"),
            new Student("Evan", 67, "Math"),
            new Student("Fiona", 88, "English")
        );

        // Overall average
        double avg = students.stream()
            .mapToInt(Student::score)
            .average()
            .orElse(0);
        System.out.printf("Overall average: %.1f points%n", avg);

        // Average by subject
        Map<String, Double> subjectAvg = students.stream()
            .collect(Collectors.groupingBy(
                Student::subject,
                Collectors.averagingInt(Student::score)
            ));
        subjectAvg.forEach((subject, average) ->
            System.out.printf("%s average: %.1f points%n", subject, average));

        // Top 3 students
        System.out.println("\n=== Top 3 Students ===");
        students.stream()
            .sorted(Comparator.comparingInt(Student::score).reversed())
            .limit(3)
            .forEach(s -> System.out.printf("%s: %d points%n", s.name(), s.score()));

        // Students scoring 80+
        List<String> highScorers = students.stream()
            .filter(s -> s.score() >= 80)
            .map(Student::name)
            .toList();
        System.out.println("\nScored 80+: " + highScorers);

        // Group by subject
        Map<String, List<Student>> bySubject = students.stream()
            .collect(Collectors.groupingBy(Student::subject));
        bySubject.forEach((subject, list) -> {
            System.out.printf("\n%s: ", subject);
            list.forEach(s -> System.out.printf("%s(%d) ", s.name(), s.score()));
        });
    }
}

Summary

Concept Description
List Ordered, allows duplicates
Set No duplicates, no guaranteed order
Map Key-value pairs
ArrayList Most common List implementation
HashMap Most common Map implementation
Generics Ensures type safety
Stream API Declarative data processing

Key Takeaways

  1. Use ArrayList and HashMap as your default choices
  2. Use Set when you need to eliminate duplicates
  3. Write type-safe code with generics
  4. Process data declaratively with the Stream API

Exercises

Exercise 1: Basic

Create a program that counts word occurrences using a Map<String, Integer>.

Exercise 2: Applied

Use the Stream API to extract only even numbers from a list of integers, sort them in descending order, and take the first 5.

Challenge

Implement a generic Stack<T> class with push, pop, peek, isEmpty, and size methods, backed internally by an ArrayList.


References


Next up: In Day 10, you'll tackle a Final Project -- bringing together everything you've learned to build a complete task management application!