← Java 강의 목록으로
모던 자바
모던 자바 · 선수: 이전 단원

19. Optional

`Optional<T>` 은 "값이 있을 수도, 없을 수도 있다" 는 의미를 **타입** 으로 명시하는 컨테이너입니다. 반환값이 null 일 수 있는 메서드에 사용하면 호출자가 null 체크를 잊지 못하게 강제할 수 있어 NPE 를 줄여 줍니다.

Java모던함수형Optional
소요 시간
약 1.5~2시간
난이도
📊 중급-고급
선수 조건
🎯 이전 단원 또는 동등 지식
결과물
`Optional<T>` 은 "값이 있을 수도, 없을 수도 있다" 는 의미를 **타입** 으로 명시하는 컨테이너입니다. 반환값이 null 일 수 있는 메서드에 사용하면 호출자가 null 체크를 잊지 못하게 강제할 수 있어 NPE 를 줄여 줍니다.

이 강의에서 배우는 것

  • 1`Optional.of` / `ofNullable` / `empty` 를 사용한다
  • 2`map` / `filter` / `orElse` / `orElseThrow` / `ifPresent` 를 안다
  • 3Optional 을 **반환 타입** 으로 사용하는 패턴을 익힌다
  • 4Optional 을 안티패턴(필드, 매개변수, 컬렉션 요소) 에 쓰지 않는다

소개

`Optional<T>` 은 "값이 있을 수도, 없을 수도 있다" 는 의미를 **타입** 으로 명시하는 컨테이너입니다. 반환값이 null 일 수 있는 메서드에 사용하면 호출자가 null 체크를 잊지 못하게 강제할 수 있어 NPE 를 줄여 줍니다.

핵심 개념

1) 생성

java
Optional<String> a = Optional.of("hi");          // null 이면 NPE
Optional<String> b = Optional.ofNullable(maybe); // null 허용
Optional<String> c = Optional.empty();

2) 값 꺼내기

java
String v = a.orElse("default");
String w = a.orElseThrow(() -> new IllegalStateException("없음"));
a.ifPresent(System.out::println);

`get()` 은 값이 없을 때 예외를 던지므로 권장하지 않습니다.

3) 변환과 필터

java
Optional<Integer> len = a.map(String::length);
Optional<String> upper = a.filter(s -> s.length() > 3).map(String::toUpperCase);

4) 안티패턴

  • 필드 타입으로 Optional 사용 X (직렬화·성능 손해)
  • 매개변수 타입으로 Optional 사용 X (오버로딩 또는 null 허용으로 충분)
  • 컬렉션 요소 타입으로 Optional 사용 X (빈 컬렉션이면 충분)

핵심 예제

예제 1 — `OptionalBasics.java` : 생성·꺼내기

java
import java.util.Optional;

public class OptionalBasics {
    public static void main(String[] args) {
        Optional<String> hi = Optional.of("hi");
        Optional<String> empty = Optional.empty();

        System.out.println(hi.isPresent());
        System.out.println(empty.isPresent());

        System.out.println(hi.orElse("default"));
        System.out.println(empty.orElse("default"));

        hi.ifPresent(s -> System.out.println("값=" + s));
        empty.ifPresent(s -> System.out.println("실행 안 됨"));
    }
}

**실행 결과**

text
true
false
hi
default
값=hi

**메모:** `ifPresent` 는 값이 있을 때만 람다를 실행합니다.

예제 2 — `OptionalMapFilter.java` : 변환·필터 체이닝

java
import java.util.Optional;

public class OptionalMapFilter {
    public static void main(String[] args) {
        Optional<String> name = Optional.of("Java");
        Optional<Integer> len = name.map(String::length);
        len.ifPresent(n -> System.out.println("길이=" + n));

        Optional<String> longName = Optional.of("hi")
            .filter(s -> s.length() > 3)
            .map(String::toUpperCase);
        System.out.println("긴 이름? " + longName);
    }
}

**실행 결과**

text
길이=4
긴 이름? Optional.empty

**메모:** `filter` 가 false 면 체인 끝까지 빈 Optional 이 흐릅니다.

예제 3 — `OptionalReturn.java` : 반환 타입으로 사용

java
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class OptionalReturn {
    static Map<String, Integer> ages = Map.of("지수", 21, "민수", 25);

    static Optional<Integer> findAge(String name) {
        return Optional.ofNullable(ages.get(name));
    }

    public static void main(String[] args) {
        System.out.println(findAge("지수").orElse(-1));
        System.out.println(findAge("미상").orElse(-1));

        findAge("민수").ifPresent(a -> System.out.println("민수=" + a));
    }
}

**실행 결과**

text
21
-1
민수=25

**메모:** 메서드 반환 타입에 Optional 을 쓰면 호출자가 null 처리를 의식적으로 하게 됩니다.

예제 4 — `OrElseThrow.java` : 없을 때 예외

java
import java.util.Optional;

public class OrElseThrow {
    public static void main(String[] args) {
        Optional<String> ok = Optional.of("값");
        Optional<String> none = Optional.empty();

        System.out.println(ok.orElseThrow());

        try {
            none.orElseThrow(() -> new IllegalStateException("필수 값 누락"));
        } catch (IllegalStateException e) {
            System.out.println("예외: " + e.getMessage());
        }
    }
}

**실행 결과**

text
값
예외: 필수 값 누락

**메모:** `orElseThrow` 는 도메인 의미가 분명한 예외와 함께 자주 사용됩니다.

전체 예제 코드 (src/)

src/OptionalBasics.java

java
import java.util.Optional;

public class OptionalBasics {
    public static void main(String[] args) {
        Optional<String> hi = Optional.of("hi");
        Optional<String> empty = Optional.empty();

        System.out.println(hi.isPresent());
        System.out.println(empty.isPresent());

        System.out.println(hi.orElse("default"));
        System.out.println(empty.orElse("default"));

        hi.ifPresent(s -> System.out.println("값=" + s));
        empty.ifPresent(s -> System.out.println("실행 안 됨"));
    }
}

src/OptionalMapFilter.java

java
import java.util.Optional;

public class OptionalMapFilter {
    public static void main(String[] args) {
        Optional<String> name = Optional.of("Java");
        Optional<Integer> len = name.map(String::length);
        len.ifPresent(n -> System.out.println("길이=" + n));

        Optional<String> longName = Optional.of("hi")
            .filter(s -> s.length() > 3)
            .map(String::toUpperCase);
        System.out.println("긴 이름? " + longName);
    }
}

src/OptionalReturn.java

java
import java.util.Map;
import java.util.Optional;

public class OptionalReturn {
    static Map<String, Integer> ages = Map.of("지수", 21, "민수", 25);

    static Optional<Integer> findAge(String name) {
        return Optional.ofNullable(ages.get(name));
    }

    public static void main(String[] args) {
        System.out.println(findAge("지수").orElse(-1));
        System.out.println(findAge("미상").orElse(-1));

        findAge("민수").ifPresent(a -> System.out.println("민수=" + a));
    }
}

src/OrElseThrow.java

java
import java.util.Optional;

public class OrElseThrow {
    public static void main(String[] args) {
        Optional<String> ok = Optional.of("값");
        Optional<String> none = Optional.empty();

        System.out.println(ok.orElseThrow());

        try {
            none.orElseThrow(() -> new IllegalStateException("필수 값 누락"));
        } catch (IllegalStateException e) {
            System.out.println("예외: " + e.getMessage());
        }
    }
}

자주 하는 실수

  1. `Optional.of(null)` → 즉시 NPE. null 가능성 있으면 `ofNullable`
  2. `get()` 무비판적 사용
  3. 필드 타입 Optional (직렬화/JPA 와 충돌)
  4. `Optional<List<X>>` 보다 빈 리스트 `List<X>` 가 더 자연스러움
  5. Optional 을 if-else 로 풀어 쓰면 의미가 사라짐 — `map`/`orElse` 체이닝 활용

정리

  • Optional 은 **반환 타입** 으로 가장 잘 어울림
  • `get()` 보다 `orElse`/`orElseThrow`/`ifPresent` 가 안전
  • `map`/`filter` 체이닝으로 if 중첩 제거

과제

# 과제 - 19. Optional

## 문제 1 — 사용자 조회

  • 파일명: `Homework01.java`
  • 핵심 개념: `Optional` 반환, `orElse`, `ifPresent`

요구사항

  • `Optional<String> findEmail(String userId)` 메서드를 만들고, 내부 Map(`id1=a@x.com, id2=b@y.com`) 에서 검색.
  • `id1`, `id3` 으로 조회해 다음 형식으로 출력.

예상 출력

text
id1 -> a@x.com
id3 -> 없음

## 문제 2 — 안전한 평균

  • 파일명: `Homework02.java`
  • 핵심 개념: `OptionalDouble`, `IntStream.average`

요구사항

  • `static OptionalDouble safeAvg(int[] arr)` — 빈 배열이면 empty.
  • `[1,2,3,4]`, `[]` 두 케이스 처리.

예상 출력

text
평균=2.5
평균 없음

## 정답 확인 직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.

정답 코드 (homework/answer/)

answer/Homework01.java

java
import java.util.Map;
import java.util.Optional;

/** 사용자 이메일 조회 with Optional. */
public class Homework01 {
    static Map<String, String> emails = Map.of("id1", "a@x.com", "id2", "b@y.com");

    public static void main(String[] args) {
        for (String id : new String[]{"id1", "id3"}) {
            String email = findEmail(id).orElse("없음");
            System.out.println(id + " -> " + email);
        }
    }

    static Optional<String> findEmail(String userId) {
        return Optional.ofNullable(emails.get(userId));
    }
}

answer/Homework02.java

java
import java.util.OptionalDouble;
import java.util.stream.IntStream;

/** 안전한 평균. */
public class Homework02 {
    public static void main(String[] args) {
        for (int[] arr : new int[][]{ {1, 2, 3, 4}, {} }) {
            OptionalDouble avg = safeAvg(arr);
            if (avg.isPresent()) {
                System.out.println("평균=" + avg.getAsDouble());
            } else {
                System.out.println("평균 없음");
            }
        }
    }

    static OptionalDouble safeAvg(int[] arr) {
        return IntStream.of(arr).average();
    }
}

직접 해 보기

bash
cd 05_모던_자바/19_Optional/src
javac OptionalReturn.java
java OptionalReturn

다음 단원

[20_Date_Time](../20_Date_Time/) — `LocalDate`/`LocalTime`/`Duration` 등 모던 시간 API 를 다룹니다.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗