Optional의 orElse, orElseGet, orElseThrow에 대해 각각의 사용법과 특징 그리고 차이를 알아보자.
orElse~의 사용법
우선 orElse~의 기본적인 사용방법을 확인해보자.
String isNull;
String name;
isNull = "loose";
name = Optional.ofNullable(isNull).orElse("test");
System.out.println(name); //isNull값이 null이 아니므로 "loose" 출력
isNull = null;
name = Optional.ofNullable(isNull).orElse("test");
System.out.println(name); //isNull값이 null이므로 "test" 출력
Optional에 올 값이 null인 경우 orElse 안에 있는 내용을 실행 시킨다.
그래서 orElse~는 if문을 이용해 처리해야 하는 명령어를 짧게 람다식처럼 처리할 수 있는 메소드라고 볼 수 있다.
Optional이란? 자바에서 Null 참조시 NullPointerException을 방지해주는 클래스를 말한다.
위의 코드에서 Optional.ofNullable()이 아닌 Optional.of() 메소드를 이용하면 NullPointerException이 발생한다.
Optional.of()는 들어올 데이터가 절대 null이 아닌 경우에만 사용하는데 null을 넣었기 때문이다.
Optional의 목적은 자바 개발 시 NullPointerException이 너무 자주 발생해서 데이터가 null이어도 처리가 가능하도록 도와주는 것이 Optional이라고 보면된다.
orElse~의 룰(리턴 값 일치)
String name1 = Optional.ofNullable("test1").orElse("test2");
너무 당연한 것이지만 orElse를 사용할 때는 Optional의 대상이 되는 값과 orElse 내의 매개변수 리턴 값은 같아야 한다.
만약 같지 않다면 orElse가 아닌 if문을 이용해서 처리해야 한다.
orElse와 orElseGet의 차이
orElse의 매개변수가 값인 경우(String 형인 "test"를 인자로 넘김)
String name1 = Optional.ofNullable("loose").orElse("test");
String name2 = Optional.ofNullable("loose").orElseGet(() -> "test");
System.out.println("name1 = " + name1);
System.out.println("name2 = " + name2);
//출력 결과
//name1 = loose
//name2 = loose
가장 눈에 먼저 보이는 차이는
orElse는 매개변수에 값을 넘기지만
orElseGet은 람다식을 넘긴다.
orElseGet 메소드를 살펴보면 Supplier라는 인터페이스 타입을 인자로 받는다고 설명되어있다.
Supplier는 인자를 받지 않고 Type T 객체를 리턴하는 함수형 인터페이스
일단은 위 말이 너무 어려우니 단순히 Supplier는 함수형 인터페이스인 람다식을 매개변수로 받는다라고 생각하면 된다.
그리고 "test"와 같은 값을 매개 변수로 orElse~를 사용하는 경우에는 orElse와 orElseGet 기능의 차이는 없다.
그래서 출력 결과는 둘다 "loose"가 출력된다.
orElse의 매개변수가 메소드인 경우
public String getName() {
System.out.println("getName 실행");
return "";
}
String name1 = Optional.ofNullable("loose").orElse(getName());
String name2 = Optional.ofNullable("loose").orElseGet(() -> getName());
System.out.println("name1 = " + name1);
System.out.println("name2 = " + name2);
//출력 결과
//getName 실행
//name1 = loose
//name2 = loose
orElse의 매개변수에 위와같이 getName()같은 메소드를 사용하면 Optional 인자가 null이 아니어도 그냥 실행되어 버린다.
그래서 getName 메소드가 강제 실행되어 "getName 실행"이 출력되었다.
물론 getName 메소드가 강제 실행되었다고 해서 name1의 값이 getName 메소드의 리턴 값인 ""가 들어가는 것은 아니다. 결정 자체는 이미 "loose"가 null이 아니기 때문에 name1에는 "loose"가 들어간다.
만약 orElse를 사용했을 때 Optional의 인자가 null일 경우에만 실행되는 중요한 메소드(결제, 대출 등등)를 넣어놨다면 null이 아님에도 실행되므로 대장애를 초래할 수 있다.
위의 케이스가 딱 사고 나기 좋은 케이스.
반면 orElseGet은 위에서 설명했듯이 매개변수로 Supplier 함수형 인터페이스로 받는다.
Supplier 특성에 의하면 메소드를 직접 실행하지 않고 필요에 의해서(Optional 값이 null일 때 orElseGet이 실행되어야 하는 경우)만 실행된다.
그러므로 매개변수로 메소드를 사용할 경우엔 orElseGet을 사용하는 것이 바람직하다.
orElse와 orElseGet에 대한 정리
orElse는 매개변수가 고정 값일 때 사용하고 orElseGet은 매개변수가 메소드일 때 사용하는 것이 좋다.
차라리 orElse를 쓰지않고 null 검사가 확실한 orElseGet 으로 고정값을 받아 모든 걸 처리할 순 없나라고 생각할 수도 있다.
orElse가 orElseGet에 비해 좋은 장점은 아래와 같다.
- orElseGet보다 짧아 가독성이 좋다.
- orElse는 클린 코드 측면에서 일반 변수를 전달하기 때문에 람다식에 비해서 직관적이다.
- Supplier 객체 만들지 않기 때문에 공간의 낭비가 없다.
위의 장점처럼 orElse는 orElseGet에 비해 장점이 크지 않아서 대부분의 상황에선 orElseGet을 사용하기도 한다.
orElseThrow의 사용법
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("user doesn't exist");
orElseThrow는 Optional의 인자가 null일 경우 예외처리를 시킨다.
JPA에서 findById가 Optional을 반환해서 위처럼 사용하기도 한다.
orElse~를 활용하는 법(JPA)
위에 설명을 이어서 하자면 여러 곳에서 활용할 수 있겠지만 개인적으로 느끼기에 가장 적극적으로 쓰인다고 생각하는건 Spring Data JPA다.
Optional<User> user = userRepository.findById(id); //정상
User user = userRepository.findById(id); //에러
Spring Data JPA 메소드는 return 값이 Optional 로 이루어져있다.
그러므로 위처럼 Optional 타입으로 받아야 정상 처리된다.
String name1 = Optional.ofNullable("test1").orElse("test2"); // 맞음
Optional name1 = Optional.ofNullable("test1").orElse("test2"); // 틀림
위에서 설명했던 코드를 다시 가져와서 복습해보자면 orElse~를 사용하는 경우 null이 아닐 시 Optional의 인자가 반환된다.
Optional 타입이 반환되는 것이 아니다.
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("user doesn't exist");
마찬가지로 Spring Data JPA 메소드는 orElse~를 사용할 수 있는데, orElse~ 문법을 사용하면 null이 아닐 경우 내부적으로 Optional의 value를 가져오도록 구현되어있다.
그러므로 이때는 User라는 Entity 객체로 받아서 처리할 수 있다.
'☕ Java' 카테고리의 다른 글
[Java] Object, Objects 차이 (1) | 2023.02.20 |
---|---|
Java에서 일급 객체(First-Class Citizen)와 일급 컬렉션(First-Class Collection)의 의미 (0) | 2022.10.25 |
Array.sort(), Collection.sort(), Comparable, Comparator 사용법 (0) | 2022.05.15 |
Java 문자열 메소드 속도 효율 및 차이 (0) | 2022.05.12 |
Java 8 Stream 사용법 (0) | 2022.05.10 |