thecodex.expert · The Codex Family of Knowledge
Java

Collections and Generics in Java

The right data structure for every job — with documented complexity, type safety, and a Stream pipeline API.

Java 21 LTSOpenJDKLast verified:
Canonical Definition

The Java Collections Framework provides type-safe data structures via generics — List (ArrayList for O(1) random access), Map (HashMap for O(1) average lookup), Set (HashSet for O(1) membership), and Queue/Deque — all with documented complexity guarantees and a Stream API for functional pipelines.

One sentence

Java Collections give you the right data structure for every job — List for ordered sequences, Set for unique values, Map for key-value pairs — all backed by well-understood algorithms with documented Big O characteristics.

List

Javalists.java
import java.util.*;

// ArrayList — dynamic array. O(1) get by index, O(1) amortised add
List<String> names = new ArrayList<>();
names.add("Priya");
names.add("Rahul");
names.add("Anita");

names.get(0);            // "Priya"
names.size();            // 3
names.contains("Rahul"); // true
names.remove("Rahul");   // removes first occurrence

// Iterate
for (String name : names) System.out.println(name);

// Immutable list (Java 9+)
List<String> fixed = List.of("a", "b", "c");   // throws on add/remove

// LinkedList — O(1) add/remove at head/tail, O(n) get by index
LinkedList<Integer> queue = new LinkedList<>();
queue.addFirst(1);
queue.addLast(2);
queue.removeFirst();   // deque operations

// Sort
Collections.sort(names);
names.sort(Comparator.naturalOrder());
names.sort(Comparator.comparingInt(String::length).reversed());

Map

Javamaps.java
// HashMap — O(1) average get/put, no guaranteed order
Map<String, Integer> scores = new HashMap<>();
scores.put("Priya", 95);
scores.put("Rahul", 88);

scores.get("Priya");                       // 95
scores.getOrDefault("Zara", 0);           // 0 — no KeyError
scores.containsKey("Rahul");               // true
scores.putIfAbsent("Anita", 72);
scores.computeIfAbsent("Zara", k -> 0);   // atomic

// Iterate entries
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// LinkedHashMap — insertion-ordered
Map<String, Integer> ordered = new LinkedHashMap<>();

// TreeMap — sorted by key, O(log n)
Map<String, Integer> sorted = new TreeMap<>();

// Immutable (Java 9+)
Map<String, Integer> fixed = Map.of("a", 1, "b", 2);
Map<String, Integer> big   = Map.ofEntries(
    Map.entry("Priya", 95),
    Map.entry("Rahul", 88)
);

Set, Queue, and choosing

Javacollections_misc.java
// HashSet — O(1) add/contains/remove, no order
Set<String> visited = new HashSet<>();
visited.add("page1");
visited.contains("page1");   // true

// TreeSet — sorted, O(log n)
Set<Integer> primes = new TreeSet<>(List.of(2, 3, 5, 7, 11));

// PriorityQueue — min-heap by default
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5); pq.offer(1); pq.offer(3);
pq.poll();   // 1 — always removes smallest

// ArrayDeque — stack and queue in one (prefer over Stack and LinkedList)
ArrayDeque<String> deque = new ArrayDeque<>();
deque.push("a");    // stack push
deque.pop();        // stack pop
deque.offer("b");   // queue enqueue
deque.poll();       // queue dequeue

Generics

Generics make collections type-safe: List<String> can only hold Strings — the compiler rejects attempts to add an Integer. Without generics, you'd need to cast on every get() and could get ClassCastException at runtime. Generics are erased at compile time — List<String> and List<Integer> are the same class at runtime. The type information only exists in source code and bytecode metadata.

Javagenerics.java
// Generic class
public class Pair<A, B> {
    private final A first;
    private final B second;

    public Pair(A first, B second) {
        this.first = first; this.second = second;
    }
    public A getFirst()  { return first; }
    public B getSecond() { return second; }

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

Pair<String, Integer> p = new Pair<>("Priya", 95);

// Generic method
public 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"

// Wildcards
void printList(List<?> list) {           // any type
    list.forEach(System.out::println);
}
void addNumbers(List<? super Integer> list) {  // Integer or supertype
    list.add(42);
}
double sumList(List<? extends Number> list) {  // Number or subtype
    return list.stream().mapToDouble(Number::doubleValue).sum();
}

Streams and collection pipelines

Javastreams_preview.java
import java.util.stream.*;

List<String> names = List.of("Priya", "Rahul", "Anita", "Priya");

// Stream pipeline — lazy, functional, not modifying the original
List<String> unique = names.stream()
    .distinct()
    .filter(n -> n.length() > 4)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());
// [ANITA, PRIYA, RAHUL]

// Collectors
Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));

long count  = names.stream().filter(n -> n.startsWith("P")).count();
Optional<String> first = names.stream().findFirst();
String joined = names.stream().collect(Collectors.joining(", "));
Commonly confused
Type erasure means generics only exist at compile time. List<String> and List<Integer> are the same class (java.util.ArrayList) at runtime. You cannot do list instanceof List<String> — the type parameter is erased. This is why you see "unchecked cast" warnings when mixing generics with reflection.
Streams do not modify the source collection. list.stream().filter(...) creates a new stream pipeline — it does not filter the original list. The source is unchanged. To get a new collection, collect: .collect(Collectors.toList()).
ArrayList vs LinkedList: almost always use ArrayList. Despite LinkedList's theoretical O(1) head insert, ArrayList's cache-friendly memory layout makes it faster in practice for most operations. Only use LinkedList when you frequently insert/remove from both ends and never access by index.
How this connects

Type erasure and heap pollution

Generic type information is erased by the Java compiler (JLS §4.6). At runtime, List<String> is just List. This was a deliberate design choice for backward compatibility with pre-generics Java code. The consequences: you cannot create generic arrays (new T[10] is illegal), you cannot use instanceof with parameterised types (x instanceof List<String>), and reflective operations work on the raw type. "Heap pollution" occurs when a variable of a parameterised type refers to an object that is not of that type — usually caused by unchecked casts and raw type usage. The @SafeVarargs annotation suppresses warnings for methods that are guaranteed not to cause heap pollution.

Sources

1
Java Language Specification §4.5 — Parameterised Types. docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.5.
2
Java SE 21 API — java.util. docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/package-summary.html.
3
Java SE 21 — Stream API. docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html.
4
Bloch, J. (2018). Effective Java (3rd ed.). Addison-Wesley.
Source confidence: HighLast verified: Primary source: Java SE Documentation · docs.oracle.com/en/java/

Sources

1
JLS §4.5 — Parameterised Types. docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.5.
2
Java SE 21 API — java.util. docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/package-summary.html.
3
Java SE 21 — Stream API. docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html.
4
Bloch, J. (2018). Effective Java (3rd ed.). Addison-Wesley.