자바에서의 null 처리
null이란
널(null)은 어떠한 값으로도 초기화 되지 않은 것을 말한다. 이렇게 초기화된 데이터는 내가 사용할 예정이다 정도로 컴파일러는 생각합니다. 그래서 참조형이지만 힙메모리상에 데이터를 만들어 내지 않는다.
null이 문제가 되는 상황 - NullPointerException (NPE)
NullPointerException은 실제 값이 아닌 null 값을 가지고 있는 객체/변수를 호출할 때 발생하는 예외다.
그래서 NullPointerException이 발생하는 경우는 다음과 같다.
- null 객체의 instance 함수(static이 아닌 method)를 호출하는 경우
- null 객체의 instance 변수에 접근하는 경우
- null 배열 객체의 length를 구하려는 경우
- null 배열 객체의 값을 index로 접근하는 경우
- application에서 NPE를 던지는 경우
NullPointerException을 해결하는 방법 - Java 8 이전
class CodeRunner{
public static void main(String[] args){
List<String> names = null;
List<String> list = getNames();
// null 인 경우
if(list != null) {
// next step
}
}
}
기존에는 null이 발생하는 상황이 생길 경우 if문을 이용하여 null을 체크 하지만, 이와 같은 경우 코드의 가독성이 떨어지고 해당 객체가 null을 가질 수 있는 객체인지, 필수 값인지 직관적으로 알 수 있는 방법이 없어 오류를 찾더라도 해결하기에 어려움이 발생할 수 있었다.
Null을 안전하게 다루는 방법 - Assert
Assert는 개발/테스트 단계에서 파라미터가 제대로 넘어왔는지, 계산이 제대로 되었는지 혹은 특정 메소드가 작동하는 한계상황(Null이 들어오면 작동안함)을 정하는 용도로 사용한다.
assertNull(o), assertNotNull(o) 함수를 통해 객체가 null인지 아닌지를 확인하고, 이에 대한 처리를 작성한다.
Null을 안전하게 다루는 방법 - Optional
상술한 if 방식의 단점 때문에, 자바 8버전부터는 Optional을 사용하여 null 값을 처리한다.
Optional는 “존재할 수도 있지만 안 할 수도 있는 객체”, 즉, “null이 될 수도 있는 객체”을 감싸고 있는 일종의 Wrapper 클래스다.
Optional에서 얻는 장점
- NPE를 유발할 수 있는 null을 직접 다루지 않아도 된다
- 수고롭게 null 체크를 직접 하지 않아도 된다
- 명시적으로 해당 변수가 null일 수도 있다는 가능성을 표현할 수 있다. (따라서 불필요한 방어 로직을 줄일 수 있다.)
Optional 객체 생성하기
Optional 의 static method 인 empty(), of(), ofNullable() 을 통해서만 Optional 객체를 생성할 수 있다.
Optional<String> opt = Optional.empty(); // 비어있는 Optional 객체를 생성
Optional<String> opt = Optional.of("value 존재"); // 값이 존재하는 Optional 객체 생성(Optional[value 존재])
// null 이거나 값이 있을 수 있는 객체를 생성한다. (null 여부를 확신할 수 없을 때)
Optional<String> optional1 = Optional.ofNullable(null);
Optional<String> optional2 = Optional.ofNullable("value 존재");
Optional 값 체크하기
- get() : Optional의 값을 가져오고, 비어있는 Optional 객체에 대해서는 NoSuchElementException 오류가 발생한다.
1
2
|
Optional<String> opt = Optional.of("value 존재"); // Optional 값이 존재
System.out.println(opt.get()); // return value 존재
|
cs |
- orElse() : 비어있는 Optional 객체에 대하여 orElse로 넘어온 값을 반환한다.
1
2
|
Optional<String> opt1 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt1.orElse("value")); // return value
|
cs |
- orElseGet() : 비어있는 Optional 객체에 대하여 orElseGet로 넘어온 함수형 값을 통해 생성된 객체를 전달한다.
1
2
|
Optional<String> opt2 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt2.orElseGet(() -> new String("새로운 값"))); // return value
|
cs |
- orElseThrow() : 비어있는 Optional 객체에 대하여 orElseThrow로 넘어온 함수형 값을 통해 오류를 발생시킨다.
1
2
|
Optional<String> opt3 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt3.orElseThrow(RuntimeException::new)); // return value
|
cs |
- map() : Optional 객체의 값을 원하는 형태로 형변환을 할 수 있다.
1
2
|
Optional<String> opt = Optional.of("TEST"); // Optional 객체 값 존재할 경우
System.out.println(opt.map(String::length)); // Optional 객체의 형태를 변경
|
cs |
- filter() : filter 메소드의 인자로 받은 람다 식이 참이면 Optional 객체를 그대로 통과시키고 거짓이면 Optional.empty()를 반환해서 추가로 처리가 안되도록 한다.
1
2
|
Optional<String> opt = Optional.of("test");
System.out.println(opt.filter(v -> v.equals("TEST"))); // true : Optional[TEST] , false : Optional.empty
|