프로그래밍 공부
[자바] #19. Optional 총정리 본문
📚 Optional 클래스의 기본 개념
Optional<T>는 값이 있을 수도 있고, 없을 수도 있는 컨테이너이다.
null을 반환하는 대신 Optional 객체를 반환하여 null 체크 로직을 줄이고, 함수형 프로그래밍 스타일로 코드를 작성할 수 있다. (NullPointerException을 방지)
📝 Optional 객체 생성 방법
Optional.of(T value)
- null이 아닌 값을 가지고 Optional 객체 생성
- null 값이면 NullPointerException 발생
Optional<String> optional = Optional.of("Hello");
System.out.println(optional.get()); // Hello
Optional.ofNullable(T value)
- null 여부에 따라 Optional 생성
- 값이 null이면 Optional.empty() 반환 (Optional.empty()는 빈 Optional 객체를 반환)
Optional<String> optional = Optional.ofNullable(null);
System.out.println(optional.isPresent()); // false
🔍 Optional 값 확인 및 처리 방법
🎯 isPresent() & isEmpty()
- 값 존재 여부 확인
Optional<String> optional = Optional.of("Test");
if (optional.isPresent()) {
System.out.println("값이 존재합니다.");
}
if (optional.isEmpty()) {
System.out.println("값이 존재하지 않습니다.");
}
⚡ ifPresent(Consumer<? super T> action)
- 값이 있으면 지정된 동작 수행
optional.ifPresent(value -> System.out.println("값: " + value));
🧱 orElse(T other)
- 값이 있으면 반환, 없으면 기본값 반환.
String value = optional.orElse("기본값");
System.out.println(value); // Test
🛠️ orElseGet(Supplier<? extends T> supplier)
- 값이 없으면 람다 표현식으로 기본값 생성
String value = optional.orElseGet(() -> "기본값 생성");
System.out.println(value);
🛡️ orElseThrow(Supplier<? extends Throwable> exceptionSupplier)
- 값이 없으면 예외 발생
String value = optional.orElseThrow(() -> new IllegalArgumentException("값이 없습니다!"));
🏃 Optional 값을 처리하는 고급 메서드
🔄 map(Function<? super T, ? extends U> mapper)
- 값이 있으면 함수를 적용하고 결과를 Optional로 반환
Optional<String> optional = Optional.of("hello");
Optional<String> upperOptional = optional.map(String::toUpperCase);
upperOptional.ifPresent(System.out::println); // HELLO
추가)
map()만 쓰면 반환 타입은 Optional<String>이다.
하지만 orElse() 를 쓰면 Optional<String>을 String으로 변환하여 변수에 대입 가능하게 해 준다.
orElse()는 Optional<T>를 T로 변환하는 역할!
참고로, orElse()는 Optional<String>에만 쓸 수 있다.
Optional<String> optional = Optional.of("hello");
String str = optional.map(s -> s.toUpperCase()).orElse("Empty"); // orElse 를 추가!
System.out.println(str);
🔀 flatMap(Function<? super T, Optional<U>> mapper)
- 중첩된 Optional을 평탄화(flatten)하여 다룰 때 사용된다.
- 즉, Optional<Optional<U>>와 같은 중첩 구조를 Optional<U>로 단순화한다.
💡 map과 flatMap의 차이점
- map: 값을 변환하고 Optional<U>를 감싼 형태로 반환한다. (Optional<Optional<U>>가 될 수 있음)
- flatMap: 값을 변환하고 중첩 없이 Optional<U>를 반환한다.
| 메서드 | 설명 |
| map(f) | 'T → R' 함수 적용 후, 결과를 Optional<R>로 감쌈 |
| flatMap(f) | 'T → Optional<R>' 함수 적용 후, Optional<R>로 평탄화 |
import java.util.Optional;
public class FlatMapExample {
public static void main(String[] args) {
Optional<String> name = Optional.of("ChatGPT");
// map 사용 예제 (중첩 Optional 발생)
Optional<Optional<String>> mapResult = name.map(FlatMapExample::getUpperCaseName);
System.out.println("map 결과: " + mapResult); // Optional[Optional[CHATGPT]]
// flatMap 사용 예제 (중첩 제거)
Optional<String> flatMapResult = name.flatMap(FlatMapExample::getUpperCaseName);
System.out.println("flatMap 결과: " + flatMapResult); // Optional[CHATGPT]
}
// 대문자로 변환 후 Optional로 반환하는 메서드
private static Optional<String> getUpperCaseName(String name) {
return (name == null) ? Optional.empty() : Optional.of(name.toUpperCase());
}
}
즉, Optional 인스턴스를 클래스의 멤버로 두는 경우에 flatMap()을 유용하게 사용할 수 있다.
🔄 filter(Predicate<? super T> predicate)
- 조건을 만족하는 값만 Optional로 유지
Optional<String> optional = Optional.of("abc");
optional.filter(value -> value.startsWith("a"))
.ifPresent(System.out::println); // abc
혹시나 까먹을까 봐.. filter 함수를 람다식이 아니라 함수형 인터페이스를 사용하여 풀어 쓴 예제는 다음과 같다.
Optional<String> optional = Optional.of("abc");
// Predicate를 사용하여 filter 함수형으로 작성
Predicate<String> startsWithA = new Predicate<String>() {
@Override
public boolean test(String value) {
return value.startsWith("a");
}
};
optional.filter(startsWithA)
.ifPresent(System.out::println); // abc
🏗️ Optional 실전 사용 예제
💡 null-safe 메서드 체이닝
import java.util.Optional;
class User {
private String name;
private Address address;
public User(String name, Address address) {
this.name = name;
this.address = address;
}
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
}
class Address {
private String city;
public Address(String city) {
this.city = city;
}
public Optional<String> getCity() {
return Optional.ofNullable(city);
}
}
public class Main {
public static void main(String[] args) {
Optional<User> user = Optional.ofNullable(getUser());
String city = user.flatMap(User::getAddress)
.flatMap(Address::getCity)
.orElse("도시 정보 없음");
System.out.println("사용자의 도시: " + city);
}
public static User getUser() {
Address address = new Address("서울");
return new User("홍길동", address);
}
}
사용자의 도시: 서울
💡 getUser() 메서드를 null 반환하도록 변경 시
public static User getUser() {
return null; // 사용자 정보가 없을 때
}
⚡ 출력 결과 (null-safe 처리 확인):
사용자의 도시: 도시 정보 없음
🌟 설명
- Optional.ofNullable(getUser()): getUser()가 null을 반환하더라도 Optional.empty()를 반환하여 NullPointerException 방지.
- flatMap(User::getAddress): Optional<Address> 반환.
- flatMap(Address::getCity): Optional<String> 반환.
- orElse("도시 정보 없음"): 값이 없을 경우 기본값 제공.
❌ 저기서 map을 써 버리면?
String city = user.map(User::getAddress) // 결과: Optional<Optional<Address>>
.map(Address::getCity) // 결과: Optional<Optional<Optional<String>>>
.orElse("도시 정보 없음"); // ❌ 컴파일 에러
'자바' 카테고리의 다른 글
| [자바] #21. 스트림(Stream) - 2편 (병렬 스트림, 중간 연산 추가 내용) (0) | 2025.02.21 |
|---|---|
| [자바] #20. 스트림(Stream) - 1편 (+ 오토 박싱 / 언박싱) (1) | 2025.02.21 |
| [자바] #18. 메서드 참조(+ 람다식에서 접근 가능한 참조변수 제한) (0) | 2025.02.20 |
| [자바] #17. 람다(Lambda)와 함수형 인터페이스 (1) | 2025.02.19 |
| [자바] #16. 네스티드(Nested) 클래스, 이너(Inner) 클래스 (0) | 2025.02.19 |