티스토리 뷰
Optional 기초 : NPE에서 벗어나보자.
들어가며
WAS log에서 NullPointerException을 발견했던 과거
- 발생 시점을 추적하기가 어려웠다. 스택 트레이스 보는데도 모르겠음.
메서드를 짜는데 객체가 없을 경우 null을 리턴 x 100(반복)
- null 체크를 계속 해야 하는 불편. 코드가 거슬린다. (if 널 존재여부...) 까먹었을까봐 불안하다.
그러던 중 스프링 강의에서 객체를 Optional로 감싸 NPE를 막는 걸 발견
- => 배워야겠다.
Optional은 람다, 스트림을 알아야 더 사용하기 좋지만...아직 사용하지 못한다. 그저 Optional만 알았을 뿐. 때문에 람다와 스트림을 몰라도 써먹을 수 있는 Optional의 기초만 정리해보도록 한다.
Optional
Optional은 java8부터 등장했다. Optional은 지네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스다. T 타입이기 때문에 Optional 타입의 객체는 모든 타입의 참조변수를 담을 수 있다.
public final class Optional<T> {
private final T value; // T 타입의 참조 변수
}
객체 생성과 초기화
Opitonal 객체를 생성할 때는 of()
또는 ofNullable()
을 사용한다. 참조변수의 값이 null일 가능성이 있으면 ofNullable을 사용해야 한다. of를 쓰면 NullPointerException이 발생하기 때문이다.
- 생성
Optional<String> optVal = Optional.of(null); //NPE 발생
Optional<String> optVal = Optional.ofNullable(null) //ok
/* 사용 예: MemberRepository */
private static Map<Long, Member> store = new HashMap<>();
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id)); //ok
// return Optional.of(store.get(id));
}
- id(key) 값으로 가져온 Member(value) 객체가 없을 수도 있으므로 Optional.of보다 Optional.ofNullable을 사용한다.
이 부분을 정리하면서 '그렇다면 무조건 ofNullable()
만 사용하면 되지 않나? of()
는 왜 쓰지?'라는 궁금증이 들었다. 스터디원 분이 알려주신 블로그 글에서 그 해답을 찾을 수 있었다. 아래는 블로그 글에 나온 Bad/Good 코드 예시다.
// Bad
return Optional.of(member.getEmail()); // member의 email이 null이면 NPE 발생
// Good
return Optional.ofNullable(member.getEmail());
email같은 경우 null일 가능성이 존재한다. 이렇게 null이 될 가능성이 조금이라도 있으면 ofNullable()
을 쓴다.
// Bad
return Optional.ofNullable("READY");
// Good
return Optional.of("READY");
of()
는 null이 발생할 리 없는 변수에서 사용한다. "READY"같은 경우 null이 될 리가 없으니 ofNullable()
보다 of()
를 쓴다.
알고 생각해보니... 메서드명 자체가 그 의미를 이미 내포하고 있더라..
- nullable한 상황 => ofNullable()
- null이 없을 상황 => of()
- 초기화
Optional<String> optVal = null; // null로 초기화. 할 수는 있으나 권장하지 않음.
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
참조변수를 기본값으로 초기화할 때는 null을 그대로 넣는 것보단 empty()
로 초기화하는 것이 좋다. NPE로 인한 장애를 피하기 위해 만든게 Optional인데 null로 초기화해서 반환해버리면 도입 의도와 맞지 않기 때문이다.
객체의 값 가져오기
Optional 객체는 값을 감싸고 있다. 그래서 감싼 값을 가져오기 위해서는 get()
또는 orElse()
를 사용한다.
- get
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); //abc
Optional<String> optVal = Optional.empty();
String str1 = optVal.get(); // NoSuchElementException
of()를 사용해서 Optional을 생성할 경우 값에 null이 들어갈 수도 있다. 이 경우 NoSuchElementException이 발생하기 때문에, orElse()로 대체할 값을 지정할 수 있다.
- orElse
String str1 = null; // String str1 = "";
str1 = "";
기존에는 String의 초기값을 null로 세팅하면 NPE를 피하기 위해 ""로 대체했다. 아예 초기값을 ""로 세팅하기도 했다.
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.orElse(""); //abc
Optional<String> optVal = Optional.empty();
String str1 = optVal.orElse(""); // "" Exception 발생 안함
orElse를 사용해 대체값을 지정하면 Optional 객체가 null일 경우 대체값을 리턴한다. 그래서 Exception이 발생하지 않는다.
대체할 값을 람다식으로 지정하는 orElseGet()
와 null일 때 지정된 예외를 발생시키는 orElseThrow()
도 있다.
객체의 값 null 체크하기
- isPresent
if (str != null) {
System.out.println(str);
}
if (Optional.ofNullable(str).isPresent()) {
System.out.println(str);
}
isPresent()
는 Optional 객체의 값이 있으면 true, null이면 false를 반환한다.
- ifPresent
Optional.ofNullable(str).ifPresent(System.out::println);
ifPresent()
는 Optional를 반환하는 findAny()나 findFirst()와 같은 최종 연산과 잘 어울린다.
마치며
이제 Optinal이 NPE를 막을 수 있다는 건 대충 알겠다. 그럼 NPE를 막을 수 있는 상황에서 모두 Optional을 써야할까? Java9의 api note에서 Optional을 만든 의도가 적혀있다.
Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.
- Optional은 주로 메서드가 반환할 결과값이 '없음'을 명백하게 표현할 필요가 있어서 만들어졌다.
- null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional을 사용해야 한다.(결과값이 없음을 표현하기 위해!)
- 메서드 반환 타입이 아닌 필드로서 Optional의 사용은 지양한다.
- Optional 변수는 그 자체가 null이 되어서는 안되고 Optional 객체를 가리켜야 한다.
- 이건 Optional 초기화를 null이 아니라 빈 객체로 하라는 말인거 같다.
참고 사이트 & 책
'Java > 기초' 카테고리의 다른 글
[java] 스트림(Stream) 기초 (0) | 2021.12.22 |
---|---|
[java] FileNameFilter로 특정 파일만 찾기 (0) | 2020.12.06 |
[java] File의 다양한 메서드 (0) | 2020.12.06 |
[java] File : getPath(), getAbsolutePath(), geCanonicalPath() 차이 (0) | 2020.12.06 |
- Total
- Today
- Yesterday
- 쿼리 파라미터 바인딩
- opencsv
- github actions 구성요소
- 이모지입력오류
- mysql 온라인 ddl
- tcp커넥션
- 콜레이션변경
- jpa 쿼리 로그
- github actions 기초
- csv 라이브러리
- online ddl
- spring retry
- csv to bean
- 코프링
- 도메인구성요소
- github actions components
- 문자집합변경
- AOP
- TCP연결
- 4Way Handshake
- read timeout
- CGLIB프록시
- spring boot3 쿼리 로그
- utf8mb3
- 엔티티와값객체
- mysql 이모지
- file
- 콜레이션
- http커넥션
- hibernate 쿼리 로그
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |