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
- Use
ArrayListandHashMapas your default choices - Use
Setwhen you need to eliminate duplicates - Write type-safe code with generics
- 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!