1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Optional<T> is container for nullable valuesβprevents NullPointerException! Create: Optional.of(value) (non-null), Optional.ofNullable(value) (may be null), Optional.empty() (no value).
- Check: isPresent(), isEmpty().
- Retrieve: orElse(default), orElseGet(supplier), orElseThrow().
- Transform: map(), flatMap(), filter(). DON'T use .get() without check! Use for return types, not fields/parameters.
- Anti-pattern: if (opt.isPresent()) opt.get() defeats purpose!
Think of package delivery. Traditional null = no tracking (package may or may not arrive, you don't know). Optional = tracking number (explicitly says "package exists" or "no package"). orElse() = backup plan ("if no package, use local store"). map() = open package, transform contents!
2. Conceptual Clarity (The "Simple" Tier)
π‘ The Analogy
- Optional: Gift box (may contain item or be empty)
- isPresent(): Shake box (hear something inside?)
- orElse(): Backup gift (if empty, use this instead)
- map(): Open box, transform contents
3. Technical Mastery (The "Deep Dive")
Optional Methods
| Method | Purpose | Returns |
|---|---|---|
| of(value) | Create (non-null) | Optional<T> |
| ofNullable(value) | Create (nullable) | Optional<T> |
| empty() | Empty Optional | Optional<T> |
| isPresent() | Has value? | boolean |
| get() | Get value (unsafe!) | T |
| orElse(T) | Value or default | T |
| orElseGet(Supplier) | Value or lazy default | T |
| orElseThrow() | Value or exception | T |
| map(Function) | Transform if present | Optional<U> |
| flatMap(Function) | Flatten nested Optional | Optional<U> |
| filter(Predicate) | Keep if matches | Optional<T> |
4. Interactive & Applied Code
java
import java.util.*;
public class OptionalDemo {
public static void main(String[] args) {
demonstrateCreation();
demonstrateRetrieval();
demonstrateTransformation();
demonstrateBestPractices();
}
// Creating Optional
static void demonstrateCreation() {
System.out.println("=== CREATING OPTIONAL ===");
// Optional.of() - throws if null
Optional<String> opt1 = Optional.of("Hello");
// Optional<String> opt2 = Optional.of(null); // β NullPointerException!
// Optional.ofNullable() - accepts null
String str = null;
Optional<String> opt3 = Optional.ofNullable(str); // β
OK
System.out.println("opt3 present? " + opt3.isPresent());
// Optional.empty()
Optional<String> opt4 = Optional.empty();
System.out.println("opt4 present? " + opt4.isPresent());
}
// Retrieving values
static void demonstrateRetrieval() {
System.out.println("\n=== RETRIEVAL ===");
Optional<String> present = Optional.of("Hello");
Optional<String> absent = Optional.empty();
// β BAD: get() without check (throws NoSuchElementException)
// String value = absent.get(); // β Exception!
// β
GOOD: orElse (immediate default)
String s1 = absent.orElse("Default");
System.out.println("orElse: " + s1);
// β
GOOD: orElseGet (lazy default - Supplier)
String s2 = absent.orElseGet(() -> {
System.out.println(" Computing default...");
return "Lazy Default";
});
System.out.println("orElseGet: " + s2);
// β
GOOD: orElseThrow
try {
String s3 = absent.orElseThrow(() ->
new IllegalStateException("Value not present!"));
} catch (IllegalStateException e) {
System.out.println("Exception: " + e.getMessage());
}
// Checking presence
if (present.isPresent()) {
System.out.println("Value: " + present.get());
}
// Better: ifPresent (functional)
present.ifPresent(v -> System.out.println("ifPresent: " + v));
// ifPresentOrElse (Java 9+)
absent.ifPresentOrElse(
v -> System.out.println("Value: " + v),
() -> System.out.println("No value!")
);
}
// Transformation
static void demonstrateTransformation() {
System.out.println("\n=== TRANSFORMATION ===");
Optional<String> opt = Optional.of("hello");
// map() - transform value
Optional<String> upper = opt.map(String::toUpperCase);
System.out.println("Uppercase: " + upper.orElse(""));
// Chained map()
Optional<Integer> length = opt
.map(String::toUpperCase)
.map(String::length);
System.out.println("Length: " + length.orElse(0));
// filter() - keep if matches
Optional<String> filtered = opt.filter(s -> s.length() > 3);
System.out.println("Filtered (>3): " + filtered.orElse("too short"));
// flatMap() - flatten nested Optional
Optional<Optional<String>> nested = Optional.of(Optional.of("Nested"));
Optional<String> flattened = nested.flatMap(o -> o);
System.out.println("Flattened: " + flattened.orElse(""));
}
// Best practices
static void demonstrateBestPractices() {
System.out.println("\n=== BEST PRACTICES ===");
// β ANTI-PATTERN: Using Optional like null check
Optional<String> opt = findUser(1);
if (opt.isPresent()) {
String user = opt.get();
System.out.println("BAD: " + user);
}
// Defeats the purpose of Optional!
// β
GOOD: Functional approach
findUser(1)
.map(String::toUpperCase)
.ifPresent(user -> System.out.println("GOOD: " + user));
// β ANTI-PATTERN: Optional field
// class User {
// Optional<String> email; // β DON'T!
// }
// β
GOOD: Optional return type
// public Optional<String> getEmail() { ... }
}
static Optional<String> findUser(int id) {
return id == 1 ? Optional.of("Alice") : Optional.empty();
}
}
// Real-world example: Repository pattern
class UserRepository {
private Map<Integer, String> users = Map.of(
1, "Alice",
2, "Bob",
3, "Carol"
);
// Return Optional (value may not exist)
public Optional<String> findById(int id) {
return Optional.ofNullable(users.get(id));
}
public static void main(String[] args) {
UserRepository repo = new UserRepository();
// Functional chain
String greeting = repo.findById(1)
.map(name -> "Hello, " + name)
.orElse("User not found");
System.out.println(greeting);
// With transformation
repo.findById(2)
.map(String::toUpperCase)
.filter(name -> name.startsWith("B"))
.ifPresent(System.out::println);
// Default value
String unknown = repo.findById(99)
.orElse("Guest");
System.out.println("Unknown user: " + unknown);
}
}5. The Comparison & Decision Layer
| Scenario | Use This | Avoid |
|---|---|---|
| Has value? | orElse(default) | isPresent() + get() |
| Transform | map(fn) | if + get() + transform |
| Lazy default | orElseGet(supplier) | orElse(expensive()) |
| Required value | orElseThrow() | get() |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "orElse() vs orElseGet()?" Answer: orElse() evaluates always, orElseGet() lazy (only if empty)!
java
// orElse() - ALWAYS evaluates default
String s1 = Optional.of("Present")
.orElse(expensiveOperation()); // β Calls even though value present!
// orElseGet() - LAZY evaluation
String s2 = Optional.of("Present")
.orElseGet(() -> expensiveOperation()); // β
NOT called!
static String expensiveOperation() {
System.out.println("Computing...");
return "Expensive";
}
// Use orElseGet() when default is expensive to compute!Pro-Tips:
- Never use Optional for fields:
java
// β BAD
class User {
Optional<String> email; // Don't!
}
// β
GOOD
class User {
String email; // Can be null
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
}- Don't use get() without check:
java
// β BAD
String s = optional.get(); // Throws if empty!
// β
GOOD
String s = optional.orElse("default");
String s = optional.orElseThrow(() -> new MyException());