최종 연산의 결과를 그냥 반환하는 게 아니라 Optional객체에 담아서 반환하는 것이다.
이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다. 널 체크를 위한 if문 없이도 NullPointerException이 발생하지 않는 코드를 작성하는 것이 가능해진다.
public final class Optional<T> {
private final T value; // T타입의참조변수
}
Optional<T>
Optional 클래스란, T타입 객체의 래퍼 클래스이다.
Optional객체를 생성할 때는 of() 또는 ofNullable()을 사용한다
그동안 null을 다룰때, 직접적으로 다루는것은 NullPointerException을 발생시킬 수 있어 위험했다.
(1) 그래서 null을 ""을 이용하여 간접적으로 다루거나
(2) if문을 사용하여 null을 체크하곤 했는데,
Optional<T>는 이러한 점을 해결하고 null을 간접적으로 다루기 위해 사용될 수 있는 래퍼클래스이다.
만일 참조변수의 값이 null일 가능성이 있으면, of()대신 ofNullable()을 사용해야한다.
of()는 매개변수의 값이 null이면 NullPointerException일 발생하기 때문이다.
Optional〈T>타입의 참조변수를 기본값으로 초기화할 때는 empty()를 사용한다.
null로 초기화하는 것이 가능하지만, empty()로 초기화 하는 것이 바람직하다
즉 null을 그대로 참조하는 것이 아닌, 주소값이 있는 객체에 담고 다시 다른 객체에서 앞선 주소를 참조하면,
해당 null값은 항상 null이 아니게 되는 것이다.
Optional<T> 객체를 생성하는 다양한 방법
of 스태틱 메소드를 이용해 Optional 객체를 생성할 수 있다.
단, of 메소드에 null을 그대로 담고 다루는 것은 NullPointException이 발생한다.
이를 방지하기 위해 ofNullable 메소드를 사용하는데, 이때는 null을 그대로 담아도 예외가 발생하지 않는다.
Optional 객체 생성시 null을 그대로 넣을 수는 있지만 바람직하지않고
empty() 메소드를 이용해 빈 객체로 초기화하는 방법을 사용할 수 있다.
마찬가지로 배열또한 Object[] objArr = null로 넣는 것이 아니라 new Object[0]을 이용해 빈채로 넣어주는등
널 포인트 익셉션을 줄이기 위한 방법으로 접근해야 한다.
Optional<T> 객체의 값 가져오기
Optional 객체에 저장된 값을 가져올 때는 get()을 사용한다.
값이 null일 때는 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정할 수 있다.
이때 orElse("") 즉, 저장된 값이 null 일경우 지정된 문자열을 반영시키는 메소드를 사용할 수 있다.
orElse( )의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet( )과
null일 때 지정된 예외를 발생시키는 orElseThrow( )가 있다.
orElseGet() 메소드의 경우 매개변수안에 람다식을 사용할 수 있다.
orElseThrow() 메소드로는 null일시 예외까지 지정해 다루어 줄 수 있다.
orElseGet(), orElseThrow()는 입력값없이 반환값만 계속 생성하는 Supplier 클래스에 속해있다.
Stream처럼 Optional객체에도 filter(), map(), flatMap()을 사용할 수 있다.
map()의 연산결과가 Optional〈Optional〈T〉〉일 때, flatMap()을 시용하면 Optional〈T〉를 결과로 얻는다.
만일 Optional객체의 값이 null이면, 이 메서드들은 아무 일도 하지 않는다.
isPresent()메소드는 null 일시 boolean 타입으로 결과를 반환한다.
ifPresent() 메소드는 null이 아닐때만 작업을 수행하며, null일경우 아무 작업도 하지 않는다.
예제를 보자
먼저, 배열에 빈값을 넣고싶다면 arr = { }; 와같이 작성해 줄 수 있는데
이경우 콘솔 출력시 배열 안에 아무것도 없으므로 길이는 0이 출력된다.
그러나 배열에 직접 null을 작성해 다뤄줄 시 에러가 발생하게 된다.
빈값인데 길이를 반환하려고 했기 때문이다.
이때 arr 참조변수 안에 new int[0] 와같이 작성해주면 에러를 발생시키지 않고도 빈값을 채워넣을 수 있다.
Optional 객체를 생성할때, 빈값을 넣어주고 싶다면 null을 직접 넣고 다루는 것은 바람직하지 않다.
empty 메소드를 넣으면 빈값인 채로 객체를 생성할 수 있다.
그러나 앞서 언급했듯 get() 메소드를 이용시, null에 접근하는 get은 예외를 반환시킨다.
get메소드를 사용해야 겠다면 try~ catch 문으로 감싸 예외 발생시 작성할 구문을 또다시 작성해주면 된다.
예외 발생시 "" 빈문자열로 초기화되도록 작성되었다.
그러나 이렇게 예외구문으로 감쌀시 코드가 길고 지저분해지므로
손쉽게 orElse() 메소드를 사용할 수 있다.
orElse는 접근한 객체의 값이 null일 경우 () 안에 작성된 지정된 문자열을 반환한다.
orElse("EMPTY")를 작성시 빈값에 대해 EMPTY문자열이 대신 출력되었다.
메서드 참조식을 람다식으로 바꾸는 연습도 계속 해보아야 한다.
orElse 메서드의 클래스 명세서를 살펴보면 삼항연산자로 정의되어있는 것을 확인할 수 있다.
해당 메서드를 사용하면 매번 삼항연산자를 사용하는 수고를 덜 수 있다.
OptionalInt, OptionalLong, OptionalDouble
Optional<T>를 사용해도 무방하지만 해당 클래스들은 기본형만을 다뤄 성능을 향상시키기위해 쓰인다.
기본형을 감싸는 래퍼 클래스 들이다.
opt와 opt2에 똑같이 빈값을 담았지만 메소드 비교시 다르게 간주됨을 확인할 수 있다.
opt에 담긴것은 int형 정수 0이고, empty에 담긴것은 빈객체 null이다.
그러므로 값이 존재하는지 묻는 is Present() 메소드를 사용하면
0값을 가진 opt는 true를, null을 가진 opt2는 false를 반환한다.
두 빈값이 같은지 묻는 equals() 메소드 사용시 역시 false를 반환하게 된다.
메소드 참조를 람다식으로 변경하면 다음과 같다.
result1변수엔 "123"을 담은뒤 null 값을 체크하는 filter 중간연산을 통과해 int형으로 반환하여 정수 123이 출력된다.
그러나 result2에는 "" 빈값을 담았기에 filter 중간연산에서 null 로 간주된다. 그러므로 null값일시 괄호안의 내용을 반환하는 orElse 연산자의 도움으로 map 메소드에서 -1로 반환된다.
result3 역시 값이 "456" 존재하므로 ifPresent 연산자를 거쳐 값이 잘 출력된다.
값 존재 여부에 따라 참,거짓을 반환하는 isPresent메소드의 활용이다.
0은 값을 존재한다고 간주해 true를 반환하고, 빈 객체만을 생성한 empty()는 null로 간주해 false를 반환한다.
null값에 getAsInt() 메소드 사용시 NoSuchElementException이 발생한다.
0값과 null 값은 다르기에 두 객체가 동일한지 묻는 equal 메소드 사용시 false가 출력된다.