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:
Mapis 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:
HashSetorder 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
- ArrayList is a row of lockers. Numbered. Walk straight to locker 7 — instant. But to insert one in the middle, you must shift everything after it.
- LinkedList is a treasure hunt. Each clue points to the next. To reach clue 7, you must follow 1→2→3→4→5→6→7. But inserting a new clue in the middle is easy — just change two arrows.
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
- HashSet is a bag of marbles. Reach in, pull one out — fast. But no order.
- TreeSet is a shelf of marbles sorted by size. Slower to place, but always
sorted, and
first()andlast()are instant.
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: "
Runnableis preferred because extendingThreaduses 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
- Byte streams move raw bytes — good for images, PDFs, audio
- Character streams move text — good for
.txt,.csv
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 afinally. 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=nullis the mark-winning detail.transientfields 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
- "
Mapis not a Collection — it stores key-value pairs on its own branch." - "
Runnableis preferred overThreadbecause extendingThreaduses up the single inheritance slot." - "Calling
run()directly is just a normal method call — no new thread." - "Without
synchronized, two threads cause a race condition — the lost-update problem." - "A stream does nothing until you reach a terminal operation."