-
Java Optional의 메소드 사용 설명서(Optional 잘 활용하기)스터디 노트 2023. 10. 16. 15:50
원본 강의 영상 : https://youtu.be/RsUTolCVm_E?si=JRr_Xu73RmoaybFP
📌 Optional이란?
Java의 Optional 함수는 JDK 8에서 추가가 되었습니다.
Optional은 쉽게 말해 있거나 없는 값을 표현할 수 있는 클래스입니다.
즉, null을 대체할 수 있는 타입입니다. null(💩)은 언제나 위기를 야기하기에 잘 다루어야 합니다
📌 Optional 만들기
Optional을 생성할 수 있는 방법은 of(), ofNullable(), empty()를 통해 생성할 수 있습니다.
// of() : null이 아닌 값으로 생성됨 Optional<String> opt = Optional.of("value"); // 만약 of()에 null을 넘기게 되면 NullPointerException이 발생함 Optional.of(null); // ofNullable() : null이 가능한 값으로 생성됨 Optional<String> opt = Optional.ofNullable(otherValue); // empty() : 빈 값으로 생성됨 Optional<String> optEmpty = Optional.empty(); Optional<String> optEmptyWithNull = Optional.ofNullable(null);
📌 값 구하기
Optional에서 값을 꺼내 쓰려면 get() 메소드를 사용하면 됩니다.
값이 있으면 해당하는 값을 리턴하지만, 값이 없는 Optional일 경우 'NoSuchElementException 💩 '을 리턴합니다.
Optional<String> opt = Optional.of("value"); String str = opt.get(); // return "value" Optional<String> opt1 = Optional.empty(); opt1.get(); // NoSuchElementException
📌 값의 유무 확인하는 방법
해당 Optional이 값을 보유하고 있는지 확인한는 방법은 두 가지가 있는데, isPresent()와 isEmpty()가 있습니다.
isPresent()는 값이 '있는지'를 확인하고, isEmpty()는 값이 '없는지'를 확인합니다.
두 메소드의 조건이 좀 다르겠죠?
참고로 isEmpty()는 자바 11에 추가되었습니다.
Optional<String> opt = Optional.of("value"); opt.isPresent(); // true opt.isEmpty(); // false Optional<String> opt1 = Optional.empty(); opt.isPresent(); // false opt.isEmpty(); // true
📌 값이 존재 할 경우
해당 Optional에 값이 존재할 경우 다음 액션을 취할 수 있는 메소드를 제공합니다.
ifPresent()와 ifPresnetOrElse() 메소드가 그것들입니다.
// 기존 코드 String value = getValue(); if (value != null) { doSomething(value); } // Optional.ifPresent() 활용시 Optional<String> opt = getValue(); opt.ifPresent(value -> doSomething());
// 기존 코드 String value = getValue(); if (value != null) { doSomething(value); } else { doOther(value); } // Optional.ifPresentOrElse 활용시 Optional<String> opt = getValue(); opt.ifPresentOrElse( value -> doSomething(value), () -> doOther(value) );
📌 값이 없을 때 활용
orElse(), orElseGet(), or()를 활용해서 값이 만약에 없을 경우 어떠한 액션을 하게 만들 수 있다.
// 기존 코드 String value = getValue(); String result = value == null ? "default" : value; // orElse() Optional<String> opt = getValue(); String result = opt.orElse("default"); // String result = opt.orElse(null); // orElseGet() Optional<String> opt = getValue(); // orElseGet(Supplier<? extends T>) String result = opt.orElseGet(() -> "default"); // or() Optional<String> opt = getValue(); // or(Supplier<? extends Optional<? extends T>>) Optional<String> result = opt.or(() -> Optional.of("default"));
먼저 orElse()의 경우 값이 있으면 값을 리턴하고, 값이 없으면 인자로 전달한 값(default)를 리턴해줍니다.
다음은 orElseGet()의 경우 값 대신 함수를 지정해주며, 값이 없을 때는 해당 함수가 호출됩니다.
or()는 추가적인 Optional을 리턴합니다. 예제에서는 "default"를 값으로 갖는 Optional을 만들어서 return을 했군요.
추가적으로 값이 없으면 익셉션을, 값이 있으면 값을 리턴하는 메소드인 orElseThrow()를 확인해보겠습니다.
// 기존 코드 Member member = repository.findById("id"); if (member == null) { throw new NoMemberException(); } member.block(); // Optional.orElseThrow 활용시 Optional<Member> opt = repository.findById("id"); // 여기서 repository도 Optional을 return해주어야 함 Member member = opt.orElseThrow(() -> new NoMemberException()); member.block();
📌 map
map이란 전달받은 함수를 실행시켜 값을 변환한 Optional을 리턴합니다.
map에 값이 없으면 빈 Optional을 리턴합니다.
// 기존 코드 Member member = ... LocalDate birthday = m.getBirthday(); int passedDays = cal(birthday); // Optional.map 활용시 Optional<Member> memOpt = ...; Optional<LocalDate> birthOpt = memOpt.map(mem -> mem.getBirthday()); Optional<Integer> pdOpt = birthOpt.map(birth -> cal(birth)); // 위 내용 축약 시 Optional<Integer> pdOpt2 = memOpt.map(mem -> mem.gerBirthday() .map(birth -> cal(birth); // 빈 Optional은 map으로 전달한 함수를 실행하지 않고 빈 Optional을 리턴 Optional<String> empty = Optional.empty(); Optional<String> empty2 = empty.map(str -> str.toUpperCase()); // empty2.isEmpty() == true
📌 flatMap
map과는 비슷하지만, flatMap은 전달 받은 함수를 이용한 값을 변환한 Optional을 리턴합니다. 즉, 리턴 타입은 Optional이 됩니다.
// Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) Optional<Member> memOpt = ...; Optional<LocalDate> birthOpt = memOpt.flatMap(mem -> Optional.ofNullable(mem.getBirthday())); Optional<Integer> pdOpt = birthOpt.flatMap(birth -> calOptional(birth));
📌 filter
filter는 조건이 충족하면 그 값을 그대로 리턴하고, 아니라면 빈 Optional을 리턴하는 메소드 입니다.
즉, 이 메소드의 리턴값도 Optional이란 얘기가 되겠지요.
// As is String str = "test"; if (str != null && str.length() > 3) { System.out.println(str); } // To be - filter() 사용시 Optional<String> strOpt = Optional.of("test"); Optional<String> filtered = strOpt.filter(str -> str.length() > 3); filered.ifPresent(str -> System.out.println(str)); // 더 축약하면 Optional<String> filtered = str.filter(str -> str.length() > 3) .ifPresent(str -> System.out.println(str);
📌 두 개의 Optional을 조합
// as is Member member = ...; if (member == null) return null; Company company = getCompany(member); if (company == null) return null; Card card = createCard(member, company); return card; // to be - 두 Optional 조합 Optional<Member> memOpt = ...; Optional<Company> compOpt = memOpt.map(mem -> getCompany(mem)); Optional<Card> card = memOpt.flatMap( mem -> compOpt.map( comp -> createCard(mem, comp); ) ); // 더 축약하면 Optional<Card> card = memOpt.flatMap(mem -> { Optional<Company> compOpt = getCompanyOptional(mem)); return compOpt.map(comp -> createCard(mem, comp)); });
또 다른 예를 보겠습니다.
// as is Member m1 = ...; Member m2 = ...; if (m1 == null && m2 == null) return null; if (m1 == null) return m2; if (m2 == null) return m1; return m1.year > m2.year ? m1 : m2; // to be Optional<Member> mem1 = ...; Optioanl<Member> mem2 = ...; Optional<Member> result = mem1.flatMap(m1 -> ... // m1이 있으면 처리할 내용들, 결과는 Optional) .or(() -> mem2); // flatMap결과가 없으면 m2를 사용, 둘 다 null이면 빈 Optional 리턴 // 위 result를 구하는 메소드를 자세히 보면(아래) Optional<Member> result = mem1.flatMap(m1 -> { // m1이 있으면 return mem2.map(m2 -> { // m2가 있으면 return m1.year > m2.year ? m1 : m2 // 최종 return 조건 실행 }).orElse(m1); // m2가 없으면 m1을 리턴 }).or(() -> m2); // flatMap 결과가 없으면 m2 사용 return result.orElse(null); // result가 없으면 null;
📌 정리
isPresent() 메소드를 사용하면 null을 사용할 때와 같은 구조가 나오게 되니 map이나 flatMap, filter, orElse, or, ifPresnet등을 활용하며 Optional을 사용해야 한다.
멋지게 Optional을 활용해보자.
'스터디 노트' 카테고리의 다른 글
@RestControllerAdvice에 대하여 (0) 2023.10.18 [Java] 불변 단 건 리스트는 Collections.singletonList() 를 활용해보세요. (0) 2023.10.17 [Java] IntStream 메소드 사용해보기 (0) 2023.10.16 자바 JDK 9 부터 JDK17까지의 주요 코딩 특징 (0) 2023.10.12 [Java] 객체 필드 Validation 손쉽게 구현하기 (feat. Bean Validation & @NotNull) (0) 2023.10.12