앞서 잠시 언급된 것과 같이 최종 연산의 결과 타입이 Optional인 경우가 있다.
최종연산에 대해 배우기 전에 Optional에 대해 배워보자
Optional<T>은 지네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스(객체아닌게 객체화 하는 래퍼클래스)이다.
그래서 Optional타입의 객체에는 모든 타입의 참조 변수를 담을 수 있다.
참고) java.util.Optional은 JDK1.8부터 추가되었다.
public final class Optional<T>{
private final T value; //T타입의 참조변수
}
최종 연산의 결과를 그냥 반환하는게 아니라 Optional객체에 담아서 반환하는 것이다.
이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다.
이제 널 체크를 위한 if문 없이도 NullPointException이 발생하지 않는 보다 간결하고 안전한 코드를 작성하는 것이 가능해진 것이다.
참고) Objects클래스에 isNull(), nonNull(), requireNonNull()과 같은 메서드가 있는 것도 널 체크를 위한 if문을 메서드안으로 넣어서 코드의 복잡도를 낮추기 위한 것이다.
*Optional객체 생성하기
Optional객체를 생성할 떄는 of() 또는 ofNullable()을 사용한다.
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(new String("abc"));
만일 참조변수의 값이 null일 가능성이 있으면, of()대신 ofNullable()을 사용해야 한다.
of()는 매개변수의 값이 null이면 NullPointerException일 ㄱ발생하기 때문이다.
Optional<String> optVal = Optional.of(null); //NullPointerException이 발생
Optional<String> optVal = Optional.ofNullable(null); //OK
Optional<T>타입의 참조변수를 기본값으로 초기화 할 때는 empty()를 사용한다. null로 초기화 하는 것이 가능하지만, empty()로 초기화 하는 것이 바람직하다.
참고)empty()는 지네릭 메서드라서 앞에 <T>를 붙였다. 추정가능하므로 생략할 수 있다.
Optional<String> optVal = null; // null로 초기화
Optional<String> optVal = Optional.<String>empty(); //빈 객체로 초기화
*Optional객체의 값 가져오기
Optional객체에 저장된 값을 가져올 때는 get()을 사용한다. 값이 null일 때는 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정할 수 있다.
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); //optVal에 저장된 값을 반환. null이면 예외발생
String str2 = optVal.orElse(""); //optVal에 저장된 값이 null일 때는, ""을 반환
orElse()의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()과 null일 때 지정된 예외를 발생시키는 orElseThrow()가 있다.
T orElseGet(Supplier<? extends T> other>
T orElseThrow(Supplier<? extends X> exceptionSupplier)
사용하는 방법은 아래와 같다.
String str3 = optVal2.orElseGet(String::new); //() -> new String()과 동일
String str4 = optVal2.orElseThrow(NullPointException::new); //null이면 예외 발생
Stream처럼 Optional객체에도 filter(), map(), flatMap()을 사용할 수 있다. map()의 연산결과가 Optional<Optional<T>>일 때, flatMap()을 사용하면 Optional<T>를 결과로 얻는다. 만일, Optional객체의 값이 null이면, 이 메서드들은 아무 일도 안한다.
int result = Optional.of("123")
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); //result=123
result = Optional.of("")
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); //result = -1
우리가 이미 알고 있는 것처럼 parseInt(문자열을 기본형으로)는 예외가 발생하기 쉬운 메서드이다. 만일 예외처리된 메서드를 만든다면 다음과 같을 것이다.
static int optStrToInt(Optional<String> optStr, int defaultValue){
try{
return optStr.map(Integer::parseInt).get();
}catch(Exception e){
return defaultValue;
}
}
isPresent()는 Optional객체의 값이 Null이면 false를, 아니면 true를 반환한다.
ifPresent(Consumer<T> block)은 값이 있으면 주어진 람다식을 실행하고, 없으면 아무일도 하지 않는다.
if(str!=null){
System.out.println(str);
}
이 코드를 ifPresent()를 이용해서 바꾸면 더 간단히 할 수 있다. 아래의 문장은 참조변수 str이 null이 아닐 때만 값을 출력하고, null이 아니면 아무 일도 일어나지 않는다.
Optional.ofNullable(str).ifPresent(System.out::println);
ifPresent()는 Optional<T>를 반환하는 findAny()나 findFirst()와 같은 최종 연산과 잘 어울린다.
Stream클래스에 정의된 메서드 중에서 Optional<T>를 반환하는 것들은 다음과 같다.
Optioanl<T> findAny()
Optional<T> findFirst()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
Optional<T> reduce(BinaryOperator<T> accumulator)
이처럼 Optional<T>를 결과로 반환하는 최종 연산 메서드들은 몇 개 없다. 심지어 max()와 min()같은 메서드들은 reduce()를 이용해서 작성된 것이다.
*OptionalInt, OptionalLong, OptionalDouble
IntStream과 같은 기본 스트림에는 Optional도 기본형을 값으로 하는 OptionalInt, OptionalLong, OptionalDouble을 반환한다. 아래의 목록은 IntStream에 정의된
메서드들이다.
OptionalInt findAny()
OptioanlInt findFirst()
OptionalInt reduce(IntBinaryOperator op)
OptionalInt max()
OptionalInt min()
OptionalInt average()
반환 타입이 Optional<T>가 아니라는 것을 제외하고는 Stream에 정의된 것과 비슷하다.
그리고 기본형 Optional에 저장된 값을 꺼낼 때에는 사용하는 메서드의 이름이 조금씩 다르다는 것에 주의
Optional 클래스 값을 반환하는 클래스
Optional<T> T get()
OptionalInt int getAsInt()
OptionalLong long getAsLong()
OptionalDouble double getAsDouble()
OptionalInt 는 다음과 같이 정의되어 있다. 앞서 래퍼 클래스에 대해서 배웠으므로, 이렇게 정의되어 있을 것이라고 짐작하는 것은 그리 어려운 일이 아니었을 것이다.
public final class OptionalInt{
private final boolean isPresent; //값이 저장되어 있으면 true
private final int value; //int 타입의 변수
기본형 int의 기본값은 0이므로 아무런 값을 갖지 않는 OptionalInt에 저장되는 값은 0일 것이다. 그러면 아래의 두 OptionalInt객체는 같은 것일까?
OptionalInt opt = OptionalInt.of(0); //OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); //OptionalInt에 0을 저장
다행히 저장된 값이 없는 것과 0이 저장된 것은 (다른것이다) isPresent라는 인스턴스변수로 구분이 가능하다.
isPresent()는 인스턴스의 값을 반환한다.
System.out.println(opt.isPresent()); //true
System.out.println(opt2.isPresent()); //false
System.out.println(opt.getAsInt()); //0
System.out.println(opt2.getAsInt()); //NoSuchElementException예외 발생
System.out.println(opt.equals(opt2)); //false
그러나 Optional객체의 경우 null을 저장하면 비어 있는 것과 동일하게 취급한다.
Optional<String> opt = Optional.ofNullable(null);
Optional<String> opt2 = Optional.empty();
System.out.println(opt.equals(opt2)); //true
optInt=5
result1=123
result2=-1
result3=456
true
false
0
optInt1=OptionalInt[0]
optInt2=OptionalInt.empty
optInt1.equals(optInt2)?false
opt=Optional.empty
opt2=Optional.empty
opt.equals(opt2) ?true
result3=123
result4=0
'자바 이론 > 스트림' 카테고리의 다른 글
최종연산 (0) | 2019.04.17 |
---|---|
스트림의 중간연산 + 자바 CompareTo메서드 (Comparable) (0) | 2019.04.15 |
스트림 만들기 (0) | 2019.04.15 |
스트림 중간연산,최종연산 목록 (0) | 2019.04.15 |