Optional이란?
- Java8에서부터 지원하는 기능
- java.util의 하위 클래스
- NPE를 방지할 수 있도록 도와준다.
- null이 올 수 있는 값을 Wrapper클래스로 감싸준다.
NPE란?
- NullPointerException
- 가장 많이 발생하는 예외중 하나
- null 여부를 검사함으로써 예외가 터지는것을 방지할 수 있다.
- 검증하는 코드는 복잡하고 번거롭다.
NPE 예시 코드
@Test
public void test01() {
String name = null;
System.out.println(name.charAt(0)); // name은 null이기 때문에 NPE가 발생한다.
if (name == null) {
name = "userA"; //null을 체크해서 값이 없는 경우 새로운 값을 삽입하는 코드
}
}
NPE 예시 코드 결과 화면
- System.out.println(name.charAt(0))에서 name의 인덱스를 검색하는 과정에서 값이 없다.
- 컴파일하는 과정에서 예외 발생 => NPE
- 아래의 if문을 출력문 위로 올려주면 null이 발생하지 않는다.
문제 해결 코드
@Test
public void test01() {
String name = null;
if (name == null) { //null인 name에 값을 삽입
name = "userA";
}
System.out.println(name.charAt(0)); //정상적으로 'u'가 출력된다.
}
NPE 해결 코드 결과 화면
- 정상적으로 name의 0번째 문자가 출력되는 것을 볼 수 있다.
위 코드의 문제점
- 지금은 단순해보이는 코드이지만 나중에 로직이 복잡해질 경우 코드또한 매우 복잡해지고 유지보수하기에 코드가 하눈에 들어오지 않는다.
- 하나하나 모두 null을 검증해야 하는데 그 때마다 if문을 남발해야한다.
- 그래서 필요한것이 Optional 클래스!
Optional의 메서드들 (Java8 공식 문서 참조)
아래와 같이 여러가지 메서드들을 Optional에서 지원하고 있다. 메서드들을 천천히 하나씩 알아보도록 하자.
Optional 활용하기
1. Optional 생성
optional을 생성한 후 해당 클래스의 내용을 살펴보면 어떻게 생성되는지 알 수 있다.
- 첫 번째, 내부에서 static 변수로 미리 객체를 생성해놓았기 때문에, 객체를 여러 번 생성하는 경우에도 오직 1개의 EMPTY객체를 공유하여 사용하고 있다. 이는 SINGLETON 패턴으로 메모리를 절약할 수 있는 방법이다.
- 두 번째, value라는 값을 갖고 있으며, Optional 객체를 생성시 value는 null값으로 지정된다.
public final class Optional<T> {
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* If non-null, the value; if null, indicates no value is present
*/
private final T value;
/**
* Constructs an empty instance.
*
* @implNote Generally only one empty instance, {@link Optional#EMPTY},
* should exist per VM.
*/
private Optional() {
this.value = null;
}
- 세 번째, Optional객체는 Wrapper클래스라 값이 없을수도 있는데 empty()메서드를 호출하여 값을 만들 수 있다.
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
2. Optional메서드들 - static 메서드
📌Optional.of(T value)
데이터가 절대 null이 아니라는 확신이 있다면 of()메서드를 사용할 수 있다.
@Test
public void ofMethod(){
String name = "userA";
//of는 절대 값이 null이 아닐때 사용한다. Optional.of()로 null을 저정하려고 하면 NPE발생
Optional<String> op = Optional.of(name);
boolean result = new String("userA").equals(op.get()); //op에 담긴 값과 새로운 "userA"의 값이 같은지 확인
System.out.println(result); //true 출력
}
📌Optional.ofNullable(T value)
어떤 값이 null이 올 수도 있고 아닐수도 있는 경우에 사용할 수 있다.
그 이후 orElse , orElseGet 메서드를 사용하서 값이 없는 경우에도 NPE발생을 예방할 수 있다.
두 메서드의 차이는 아래와 같다.
- orElse : 파라미터로 값(원시형)을 받는다.
- orElseGet : 파라미터로 함수형 인터페이스(참조형)를 받는다.
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
@Test
public void ofNullableMethod(){
String name = null;
Optional<String> op = Optional.ofNullable(name);//name은 null일수도있고 아닐수도 있다.
String result = op.orElse("이 값은 null입니다."); //값이 없다면 이 문장을 출력
System.out.println("result = " + result);
String userA = op.orElseGet(() -> new String("userA")); //해당 값이 존재하지 않는다면 새로운 객체를 생성
System.out.println("userA = " + userA);
}
더 자세한 orElse와 orElseGet차이 예시
우선 아래 코드와 결과 코드를 확인하자
예시 코드
@Test
public void orElseAndElseGetMethod(){
String name = "userA";
String orElseResult = Optional.ofNullable(name).orElse(getName());
System.out.println("orElseResult = " + orElseResult);
String orElseGetResult = Optional.ofNullable(name).orElseGet(this::getName);
System.out.println("orElseGetResult = " + orElseGetResult);
}
private String getName() {
System.out.println("getName메서드 호출");
return "userB";
}
결과
- orElse는 해당 Optional객체의 값이 null이든 아니든 항상 getName메서드를 호출 시킨다.
- orElseGet은 getName메서드를 null일 경우에만 호출시킨다.
- 이 차이는 매우 중요하다. orElse를 잘못 사용하면 getName을 호출시키는 순간 잘못된 결과를 초래할 수 있다.
잘못된 결과를 초래하는 코드(JPA사용)
아래코드에서는 어떤 잘못된 결과를 초래할 수 있을까?
만약 User의 Name테이블이 PK이거나 Unique가 걸려있을 경우 제약조건에 의해 orElse에서 createUserWithName을 호출하는 순간 예외가 발생한다.
또한 orElse는 해당메서드를 호출하므로 자원또한 낭비하게된다.
이러한 코드가 수행되면 서버에 장애가 생길 수 있으므로 orElse가 아닌 orElseGet을 사용해야 한다.
public void findByUserName(String Name) {
return userRepository.findByUserName(Name)
.orElse(createUserWithName(Name)); //호출되는 순간 new User를 생성해버린다.
}
private String createUserWithName(String Name) {
User newUser = new User(Name);
return userRepository.save(newUser);
}
orElseGet으로 수정한 코드
public void findByUserName(String Name) {
return userRepository.findByUserName(Name)
.orElseGet(this::createUserWithName(Name)); null일 경우에만 메서드 호출
}
private String createUserWithName(String Name) {
User newUser = new User(Name);
return userRepository.save(newUser);
}
아래의 글은 망나니개발자님의 블로그를 참고하여 만들었습니다.
'JAVA-기초 > JAVA기본' 카테고리의 다른 글
[Java- Algorithm] 너비 우선 탐색법(BFS과 큐(Queue) (0) | 2024.02.28 |
---|---|
[자료구조 - Graph 와 DFS] Graph와 DFS란? (0) | 2024.02.24 |
[Java] 객체지향개념(OOP) 캡슐화와 정보은닉 (3) | 2023.09.03 |
객체지향 프로그래밍 5가지 설계 원칙, SOLID- 단일책임의 원칙 (64) | 2023.08.12 |
자바(Java) - 쓰레드란? start와 run의차이 (0) | 2023.04.03 |