Exam Kit Collections & Advanced Java

Collections & Advanced Java

These are the two units your PPT deck is missing. Your 8-unit PDF folder stops at Unit 5 of the college syllabus. Nothing in it covers Collections (Unit 6) or Multithreading, File I/O, Lambda, Stream API (Unit 7) — together 18% of the paper, about 11 marks.

Everything here is rebuilt from the college's own lecture slides, so the words match what your examiner expects.

Java version: all code targets Java 25 LTS (2025) and also compiles on Java 21 and Java 17.


Part A · Collections (Unit 6)

A1. Why collections exist

In simple words

An array is an egg carton — exactly 12 slots, decided before you start, and you can't add a 13th egg.

A collection is a shopping bag — it grows when you put things in and shrinks when you take them out.

That's the whole idea. Collections are arrays that can change size.

Think of it like

Array Collection
A fixed egg carton A shopping bag
You must say the size first Just keep adding
Only holds one type Only holds one type, but checked by the compiler

What you gain

Gain Meaning
Grows by itself No more deciding the size up front
Ready-made tools add, remove, contains, sort — already written
One interface, many options Learn List once, use it with any version
Type safety The compiler stops you putting a String in a number list

A2. The three you need — List, Set and Map

In simple words

Collection Real-life picture Duplicates? Order?
List A shopping list ✅ Yes, write "milk" twice ✅ Kept
Set A guest list ❌ One entry per person ❌ Usually not
Map A phone book ❌ One number per name Depends

⚠️ Important: Map is not a Collection. It sits on its own branch because it stores pairs (name → number), not single items. Say this in the exam — it's a favourite trap.

The family tree

Collection
├── List        →  ArrayList, LinkedList
├── Set         →  HashSet, TreeSet, LinkedHashSet
└── Queue       →  PriorityQueue, ArrayDeque

Map             →  HashMap, TreeMap, LinkedHashMap   ← separate branch

Code — all three in one go

import java.util.*;

public class CollectionsDemo {
    public static void main(String[] args) {

        // ---------- LIST: ordered, duplicates allowed ----------
        ArrayList<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Python");
        list.add("Java");                     // duplicate is kept

        System.out.println("List       : " + list);
        System.out.println("size       : " + list.size());      // 3
        System.out.println("get(0)     : " + list.get(0));      // Java

        // ---------- SET: no duplicates ----------
        HashSet<String> set = new HashSet<>(list);
        System.out.println("Set        : " + set);
        System.out.println("size       : " + set.size());       // 2

        // ---------- SORTED SET ----------
        TreeSet<Integer> sorted = new TreeSet<>();
        sorted.add(5);
        sorted.add(1);
        sorted.add(9);
        sorted.add(1);                        // ignored, already there
        System.out.println("TreeSet    : " + sorted);

        // ---------- MAP: key -> value ----------
        HashMap<Integer, String> students = new HashMap<>();
        students.put(101, "Aarav");
        students.put(102, "Diya");
        students.put(101, "Replaced");        // same key overwrites

        System.out.println("Map        : " + students);
        System.out.println("get(101)   : " + students.get(101));
        System.out.println("size       : " + students.size());  // 2
    }
}

Output

List       : [Java, Python, Java]
size       : 3
get(0)     : Java
Set        : [Java, Python]
size       : 2
TreeSet    : [1, 5, 9]
Map        : {101=Replaced, 102=Diya}
get(101)   : Replaced
size       : 2

Note: HashSet order can differ on your machine. That's expected — it has no order. Say so in your answer.


A3. ArrayList vs LinkedList

Think of it like

The difference

ArrayList LinkedList
Under the hood A resizable array A chain of linked nodes
get(5) — read one Instant ✅ Slow — must walk there ❌
Insert in the middle Slow — shifts everything Fast ✅
Memory used Less More (each node stores links)
Use when Mostly reading Lots of adding/removing

Code — same operations, both lists

import java.util.*;

public class ListCompare {
    public static void main(String[] args) {

        ArrayList<String> al = new ArrayList<>();
        al.add("A");
        al.add("B");
        al.add("C");
        System.out.println("ArrayList : " + al);
        System.out.println("get(1)    : " + al.get(1));       // B
        al.add(1, "X");                                       // insert at index 1
        System.out.println("after add(1,X) : " + al);
        al.remove("B");
        System.out.println("after remove   : " + al);

        LinkedList<String> ll = new LinkedList<>();
        ll.add("A");
        ll.add("B");
        ll.addFirst("Start");                                 // LinkedList special
        ll.addLast("End");
        System.out.println("\nLinkedList : " + ll);
        System.out.println("getFirst   : " + ll.getFirst());
        System.out.println("getLast    : " + ll.getLast());
    }
}

Output

ArrayList : [A, B, C]
get(1)    : B
after add(1,X) : [A, X, B, C]
after remove   : [A, X, C]

LinkedList : [Start, A, B, End]
getFirst   : Start
getLast    : End

Exam tip

Default to ArrayList. Only switch to LinkedList when the question mentions lots of inserting or deleting. Write that line.


A4. HashSet vs TreeSet

Think of it like

The difference

HashSet TreeSet
Order ❌ None ✅ Sorted
Under the hood Hash table Balanced tree
Speed Faster ✅ Slower
Allows null? ✅ One null ❌ No — throws an error
Use when Just need "no duplicates" Need them sorted

Code

import java.util.*;

public class SetCompare {
    public static void main(String[] args) {

        int[] data = {5, 3, 8, 3, 1, 5, 9, 1};

        HashSet<Integer> hash = new HashSet<>();
        TreeSet<Integer> tree = new TreeSet<>();

        for (int n : data) {
            hash.add(n);
            tree.add(n);
        }

        System.out.println("Original : " + Arrays.toString(data));
        System.out.println("HashSet  : " + hash + "   (no order)");
        System.out.println("TreeSet  : " + tree + "   (sorted)");
        System.out.println("TreeSet first() : " + tree.first());
        System.out.println("TreeSet last()  : " + tree.last());
        System.out.println("TreeSet headSet(5) : " + tree.headSet(5));
    }
}

Output

Original : [5, 3, 8, 3, 1, 5, 9, 1]
HashSet  : [1, 3, 5, 8, 9]   (no order)
TreeSet  : [1, 3, 5, 8, 9]   (sorted)
TreeSet first() : 1
TreeSet last()  : 9
TreeSet headSet(5) : [1, 3]

Exam tip

"Why is TreeSet sorted?" — Because internally it's a balanced binary search tree. Every insert is placed by comparing values. That sentence earns the mark.


A5. HashMap vs TreeMap

The difference

HashMap TreeMap
Order of keys ❌ None ✅ Sorted by key
Speed Faster ✅ Slower
Null key ✅ One allowed ❌ Not allowed
Use when Fast lookup by key Need keys in sorted order

Code — student records (the exact sample-paper question)

import java.util.*;

public class MapDemo {
    public static void main(String[] args) {

        HashMap<Integer, String> students = new HashMap<>();

        students.put(103, "Kabir");
        students.put(101, "Aarav");
        students.put(102, "Diya");
        students.put(104, "Meera");

        System.out.println("HashMap (no order) : " + students);

        System.out.println("get(102)           : " + students.get(102));
        System.out.println("get(999)           : " + students.get(999));  // null
        System.out.println("containsKey(103)   : " + students.containsKey(103));

        // iterate every pair
        System.out.println("\n--- entrySet ---");
        for (Map.Entry<Integer, String> e : students.entrySet()) {
            System.out.println("Roll " + e.getKey() + " -> " + e.getValue());
        }

        // same data, sorted by key
        TreeMap<Integer, String> sorted = new TreeMap<>(students);
        System.out.println("\nTreeMap (sorted)   : " + sorted);

        students.remove(104);
        System.out.println("after remove(104)  : " + students);
    }
}

Output

HashMap (no order) : {101=Aarav, 102=Diya, 103=Kabir, 104=Meera}
get(102)           : Diya
get(999)           : null
containsKey(103)   : true

--- entrySet ---
Roll 101 -> Aarav
Roll 102 -> Diya
Roll 103 -> Kabir
Roll 104 -> Meera

TreeMap (sorted)   : {101=Aarav, 102=Diya, 103=Kabir, 104=Meera}
after remove(104)  : {101=Aarav, 102=Diya, 103=Kabir}

A6. Iterators

In simple words

An Iterator walks through a collection one item at a time, without you needing to know how the collection stores things.

The three methods

Method Job
hasNext() "Is there another one?"
next() "Give me the next one"
remove() "Delete the one I just took"

The trap everyone falls into

// ❌ This CRASHES with ConcurrentModificationException
for (String name : list) {
    if (name.equals("Bob")) {
        list.remove(name);          // changing the list WHILE looping
    }
}
// ✅ This is safe
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("Bob")) {
        it.remove();                // the iterator knows how to do this
    }
}

Code

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {

        List<String> names = new ArrayList<>(
                Arrays.asList("Aarav", "Bob", "Kabir", "Bob"));

        System.out.println("Before : " + names);

        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            if (it.next().equals("Bob")) {
                it.remove();
            }
        }

        System.out.println("After  : " + names);

        // for-each is fine when you are only READING
        System.out.print("for-each : ");
        for (String n : names) System.out.print(n + " ");
        System.out.println();
    }
}

Output

Before : [Aarav, Bob, Kabir, Bob]
After  : [Aarav, Kabir]
for-each : Aarav Kabir 

Exam tip

ConcurrentModificationException — that name alone is worth a mark. It means "you changed the list while looping over it."


A7. Generics

In simple words

Generics let you say what type a collection holds. ArrayList<String> means "a list that can only hold Strings." The compiler enforces it.

Think of it like

A labelled jar. A jar marked "SUGAR" won't accept salt. Without the label, anything goes — and you find out at dinner.

Code

import java.util.*;

class Box<T> {                       // T = "some type, decided by the user"
    private T value;

    Box(T value) { this.value = value; }

    T get() { return value; }

    void show() {
        System.out.println("Value: " + value
                         + "  type: " + value.getClass().getSimpleName());
    }
}

public class GenericsDemo {
    public static void main(String[] args) {

        Box<String>  b1 = new Box<>("Parul");
        Box<Integer> b2 = new Box<>(2026);
        Box<Double>  b3 = new Box<>(9.81);

        b1.show();
        b2.show();
        b3.show();

        // type safety in action
        List<String> names = new ArrayList<>();
        names.add("Aarav");
        // names.add(123);          ❌ compile error - caught before running

        // no cast needed
        String first = names.get(0);   // already a String
        System.out.println("First: " + first);
    }
}

Output

Value: Parul  type: String
Value: 2026  type: Integer
Value: 9.81  type: Double
First: Aarav

Benefits — the four marks

# Benefit
1 Type safety — mistakes caught at compile time
2 No casting — get(0) already returns the right type
3 Reuse — one Box<T> works for every type
4 No ClassCastException at run time

Exam tip

The <T> is a placeholder. When you write Box<String>, every T becomes String. That's it.


A8. Which collection should I pick?

A single table that answers half the Unit 6 questions.

I need to… Use Why
Keep order and allow duplicates ArrayList Fast reading by index
Add/remove a lot at the ends LinkedList Fast inserting
Remove duplicates, order doesn't matter HashSet Fastest check
Remove duplicates and sort TreeSet Kept sorted
Remove duplicates, keep insertion order LinkedHashSet Both benefits
Look something up by a key HashMap Fastest lookup
Look something up and have keys sorted TreeMap Sorted
Process in arrival order ArrayDeque Queue behaviour
Always get the smallest/largest first PriorityQueue Heap

Part B · Multithreading (Unit 7)

B1. What multithreading is

In simple words

Multithreading means one program doing several things at the same time.

Normally Java runs one line after another. With threads, two blocks of code run alongside each other.

Think of it like

One chef, four burners. Pasta on one, sauce on another, bread in the oven. One kitchen, several things happening at once.

Process Thread
A whole separate kitchen A burner inside one kitchen
Own memory Shares the kitchen's memory
Expensive to start Cheap to start
Chrome window A tab inside Chrome

B2. Two ways to create a thread

Way 1 — extend Thread

class MyThread extends Thread {
    public void run() {                    // what the thread does
        for (int i = 1; i <= 3; i++) {
            System.out.println(getName() + " -> " + i);
        }
    }
}

public class Demo1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("Thread-A");
        t2.setName("Thread-B");
        t1.start();                        // NOT run()
        t2.start();
    }
}

Way 2 — implement Runnable ← the better way

class MyTask implements Runnable {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + " -> " + i);
        }
    }
}

public class Demo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyTask(), "Thread-X");
        Thread t2 = new Thread(new MyTask(), "Thread-Y");
        t1.start();
        t2.start();
    }
}

Why Runnable is better

Reason Explanation
Keeps your one inheritance slot extends Thread uses it up — you can't extend anything else
Cleaner design The task is separate from the thread machinery
Reusable The same Runnable can back many threads
Works with thread pools The professional standard

One line for the exam: "Runnable is preferred because extending Thread uses up the single inheritance slot."


B3. start() vs run() — always asked

start() run()
Makes a new thread ✅ Yes ❌ No
Runs alongside main ✅ Yes ❌ No — runs in the current thread
Can be called twice ❌ Throws an error ✅ Yes

Calling run() directly is just a normal method call. Nothing runs in parallel. That's the whole answer.


B4. Thread life cycle

        NEW
         │  start()
         ▼
     RUNNABLE ◄───────────┐
         │                │
         │ sleep()        │ timeout /
         │ wait()         │ notify()
         │ join()         │
         ▼                │
   WAITING / BLOCKED ─────┘
         │
         │ run() finishes
         ▼
    TERMINATED

The six states

Java's Thread.State enum defines six states — not five. The three waiting states are separate and examiners do check this.

State Meaning How you leave it
NEW Created, start() not called call start()
RUNNABLE Ready or currently running the scheduler picks another
BLOCKED Waiting to acquire a lock the lock is released
WAITING wait() / join() with no timeout notify() / the thread ends
TIMED_WAITING sleep(ms) / wait(ms) / join(ms) the timeout expires
TERMINATED Finished, dead — cannot restart

If the paper says "draw the life cycle", the classic five-box diagram is accepted: New → Runnable → Running → Blocked → Dead. If it says "name the states", give all six above.

Code — watching the states

public class StateDemo {
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(() -> {
            try { Thread.sleep(500); } catch (InterruptedException e) { }
        }, "Watcher");

        System.out.println("1. after new  : " + t.getState());   // NEW
        t.start();
        System.out.println("2. after start: " + t.getState());   // RUNNABLE
        Thread.sleep(100);
        System.out.println("3. sleeping   : " + t.getState());   // TIMED_WAITING
        t.join();
        System.out.println("4. finished   : " + t.getState());   // TERMINATED
    }
}

Output

1. after new  : NEW
2. after start: RUNNABLE
3. sleeping   : TIMED_WAITING
4. finished   : TERMINATED

B5. Race conditions and synchronized

The problem

Two threads both read count = 5, both add 1, both write 6. One addition vanished. This is called the lost-update problem, and it's a race condition.

Think of it like

Two people editing the same Google Doc at the same time, both saving. Whoever saves last wipes out the other's change.

The fix

synchronized puts a lock on the method. Only one thread can be inside at a time. The other waits its turn.

Code — two threads, one counter

class Counter {
    private int count = 0;

    // synchronized = only one thread at a time
    public synchronized void increment() {
        count++;
    }

    public int getCount() { return count; }
}

class Worker implements Runnable {
    private Counter counter;
    Worker(Counter c) { this.counter = c; }

    public void run() {
        for (int i = 0; i < 100000; i++) {
            counter.increment();
        }
    }
}

public class SyncDemo {
    public static void main(String[] args) throws InterruptedException {

        Counter counter = new Counter();

        Thread t1 = new Thread(new Worker(counter), "Thread-1");
        Thread t2 = new Thread(new Worker(counter), "Thread-2");

        t1.start();
        t2.start();

        t1.join();               // wait for both to finish
        t2.join();

        System.out.println("Expected: " + 200000);
        System.out.println("Actual  : " + counter.getCount());
    }
}

Output

Expected: 200000
Actual  : 200000

Run it without synchronized — this is the demo worth marks

Delete the word synchronized and run it again:

Expected: 200000
Actual  : 174823

A different number every time. That is a race condition. Write both outputs in your answer — it proves you understand, better than any paragraph.


B6. synchronized method vs synchronized block

Method Block
What gets locked The whole method Only what's in the braces
Lock held for Longer Shorter — better
Speed Slower Faster
// method-level
public synchronized void increment() {
    count++;
}

// block-level - lock only the critical part
public void increment() {
    System.out.println("before");       // not locked
    synchronized (this) {
        count++;                        // locked
    }
    System.out.println("after");        // not locked
}

Part C · Files (Unit 7)

C1. Byte streams vs character streams

In simple words

Think of it like

Moving furniture vs moving letters. Furniture goes in a truck (bytes). Letters go in an envelope addressed in a language you can read (characters).

Byte Stream Character Stream
Moves 8-bit bytes 16-bit characters
Main classes FileInputStream, FileOutputStream FileReader, FileWriter
Good for Images, audio, PDFs Text files
Handles encoding ❌ No ✅ Yes

C2. Write and read a file

import java.io.*;

public class FileDemo {
    public static void main(String[] args) {

        String path = "demo.txt";
        String text = "Hello students.\nWelcome to Java file handling.";

        // ---------- WRITE ----------
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(text.getBytes());          // text -> bytes
            System.out.println("Written to " + path);
        } catch (IOException e) {
            System.out.println("Write error: " + e.getMessage());
        }

        // ---------- READ ----------
        try (FileInputStream fis = new FileInputStream(path)) {
            int ch;
            StringBuilder sb = new StringBuilder();
            while ((ch = fis.read()) != -1) {    // -1 means end of file
                sb.append((char) ch);
            }
            System.out.println("Read back:");
            System.out.println(sb);
        } catch (IOException e) {
            System.out.println("Read error: " + e.getMessage());
        }
    }
}

Output

Written to demo.txt
Read back:
Hello students.
Welcome to Java file handling.

What is try ( ... )? It's called try-with-resources. It closes the file automatically when the block ends, so you don't need a finally. Say that — it's a mark.


C3. Serialization

In simple words

Serialization turns an object into a stream of bytes so you can save it to a file or send it over a network. Deserialization turns it back into an object.

Think of it like

Freeze-drying a meal, shipping it, then adding water. The meal survives the journey in a compact form.

import java.io.*;

class Student implements Serializable {     // must implement this marker
    private static final long serialVersionUID = 1L;

    int rollNo;
    String name;
    transient String password;              // NOT saved

    Student(int rollNo, String name, String password) {
        this.rollNo = rollNo;
        this.name = name;
        this.password = password;
    }
}

public class SerializationDemo {
    public static void main(String[] args) {

        Student s = new Student(101, "Aarav", "secret");

        // SAVE
        try (ObjectOutputStream out =
                 new ObjectOutputStream(new FileOutputStream("student.ser"))) {
            out.writeObject(s);
            System.out.println("Saved: " + s.name);
        } catch (IOException e) { System.out.println(e.getMessage()); }

        // LOAD
        try (ObjectInputStream in =
                 new ObjectInputStream(new FileInputStream("student.ser"))) {
            Student d = (Student) in.readObject();
            System.out.println("Loaded: " + d.rollNo + " " + d.name
                               + " password=" + d.password);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }
}

Output

Saved: Aarav
Loaded: 101 Aarav password=null

password=null is the mark-winning detail. transient fields are skipped, so they come back as the default value.

The three names

Name Job
Serializable An empty marker interface — just flags the class as allowed
ObjectOutputStream Writes the object (writeObject)
ObjectInputStream Reads it back (readObject)

Part D · Modern Java (Unit 7)

D1. Lambda expressions

In simple words

A lambda is a short way to write a small method without all the ceremony.

Think of it like

Instead of writing a full recipe card for "add two numbers", you just write (a, b) -> a + b. Same meaning, one line.

interface Calculator {
    int operate(int a, int b);
}

public class LambdaDemo {
    public static void main(String[] args) {

        // the old way
        Calculator add1 = new Calculator() {
            public int operate(int a, int b) { return a + b; }
        };

        // the lambda way
        Calculator add      = (a, b) -> a + b;
        Calculator subtract = (a, b) -> a - b;
        Calculator multiply = (a, b) -> a * b;

        System.out.println("Add:      " + add.operate(10, 5));
        System.out.println("Subtract: " + subtract.operate(10, 5));
        System.out.println("Multiply: " + multiply.operate(10, 5));
    }
}

Output

Add:      15
Subtract: 5
Multiply: 50

Functional interface

An interface with exactly one method can be written as a lambda. Those are called functional interfaces.

Three built-in ones worth knowing:

Interface Takes Returns
Runnable nothing nothing
Comparator two things an int (which is bigger)
Predicate<T> one thing true / false

D2. Stream API

In simple words

A stream is a pipeline for a collection. You say what you want — filter the evens, square them, collect them — and Java does the looping.

Think of it like

An assembly line. Items go in one end. Each station does one job. Finished items come out the other end. You never write the loop.

The pipeline

list  ──►  filter  ──►  map  ──►  collect
(source)   (keep some)  (change)   (gather)

Code — keep the even numbers and square them

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

public class StreamDemo {
    public static void main(String[] args) {

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

        List<Integer> evenSquares = numbers.stream()
                .filter(n -> n % 2 == 0)          // keep only evens
                .map(n -> n * n)                  // square each one
                .collect(Collectors.toList());    // gather into a list

        System.out.println("Original     : " + numbers);
        System.out.println("Even squares : " + evenSquares);

        long count = numbers.stream().filter(n -> n % 2 == 0).count();
        int sum = numbers.stream().reduce(0, Integer::sum);

        System.out.println("Even count   : " + count);
        System.out.println("Sum          : " + sum);
    }
}

Output

Original     : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Even squares : [4, 16, 36, 64, 100]
Even count   : 5
Sum          : 55

The operations

Operation Type Job
filter(x -> ...) middle Keep only matching items
map(x -> ...) middle Change each item
sorted() middle Sort
distinct() middle Remove duplicates
collect(...) end Gather the result
forEach(...) end Do something to each
count() end How many
reduce(...) end Combine into one value

The middle ones do nothing on their own. Work only starts when you reach an end operation. That's a favourite exam point.


D3. Optional

In simple words

Optional is a box that might be empty. Instead of returning null and hoping nobody trips over it, you return a box and say "check inside first."

Think of it like

A parcel that may or may not have something in it. You don't open it blindly — you check the label first.

import java.util.*;

public class OptionalDemo {
    public static void main(String[] args) {

        Map<String, Integer> marks = new HashMap<>();
        marks.put("Aarav", 88);
        marks.put("Diya",  76);

        // a name that exists
        Optional<Integer> found = Optional.ofNullable(marks.get("Aarav"));
        System.out.println("Aarav: " + found.orElse(-1));      // 88

        // a name that doesn't
        Optional<Integer> missing = Optional.ofNullable(marks.get("Zara"));
        System.out.println("Zara : " + missing.orElse(-1));    // -1 — safe default

        // throw if absent
        try {
            int m = Optional.ofNullable(marks.get("Zara"))
                            .orElseThrow(() -> new NoSuchElementException("Not found"));
        } catch (NoSuchElementException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Output

Aarav: 88
Zara : -1 — safe default
Error: Not found

Why it's better than null

With null With Optional
You forget to check → crash The type reminds you it may be empty
if (x != null) everywhere orElse, ifPresent, orElseThrow
No signal in the method name The signature says "may be empty"

Quick self-test

Cover the answers and try these.

# Question Answer
1 Which is faster, ArrayList.get(5) or LinkedList.get(5)? ArrayList — instant
2 Which collection sorts itself? TreeSet (and TreeMap)
3 Can a Map hold duplicate keys? No
4 Is Map a Collection? No — separate branch
5 What exception does removing inside a for-each throw? ConcurrentModificationException
6 start() or run() to make a real thread? start()
7 Does sleep() release the lock? No
8 Which keyword stops two threads clashing? synchronized
9 Which interface flags a class as savable to disk? Serializable
10 What does transient do? Skips the field when saving
11 Which stream step actually does the work? The end operation
12 What does Optional.orElse(x) return if empty? x

The five sentences worth memorising

  1. "Map is not a Collection — it stores key-value pairs on its own branch."
  2. "Runnable is preferred over Thread because extending Thread uses up the single inheritance slot."
  3. "Calling run() directly is just a normal method call — no new thread."
  4. "Without synchronized, two threads cause a race condition — the lost-update problem."
  5. "A stream does nothing until you reach a terminal operation."