앞서 잠시 언급된 것과 같이 최종 연산의 결과 타입이 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


import java.util.*;
 
public class OptionalEx1 {
    public static void main(String[] args) {
        Optional<String> optStr = Optional.of("abcde");
        Optional<Integer> optInt = optStr.map(String::length);
        System.out.println("optStr="+optStr.get());
        System.out.println("optInt="+optInt.get());
 
        String str = null;
        int result1 = Optional.of("123")
                            .filter(x->x.length() > 0)
                            .map(Integer::parseInt).get(); //여기서 get은 뭐지? Optional객체에 저장된 값을 가져올때
        int result2 = Optional.of("")
                            .filter(x->x.length() > 0)
                            .map(Integer::parseInt).orElse(-1); //Integer함수에 있는 parseInt객체를 쓴다는 말일 뿐이다.
        //여기서 orElse는 객체에 저장된 값을 가져올때 null일때 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정한다.
        //""은 값이 null이라고 인식하고 null은 왜 에러가 나는거지? -> 중간연산과정 filter는 x->x.length() > 0 을 제외하고 map은 그거를 숫자로 바꿀때 값이 null이어서 -1 출력
        //filer는 주어진 조건에 맞지 않는 요소는 다 제거 즉, length>0이아닌 길이가 없는 것들 다 제거
 
        System.out.println("result1="+result1);//123
        System.out.println("result2="+result2);//-1
 
        Optional.of("456").map(Integer::parseInt)
                            .ifPresent(x->System.out.printf("result3=%d%n",x));//456
 
        OptionalInt optInt1 = OptionalInt.of(0); //0을 저장
        OptionalInt optInt2 = OptionalInt.empty(); //빈 객체를 생성
 
        System.out.println(optInt1.isPresent()); //true
        System.out.println(optInt2.isPresent()); //false
 
        System.out.println(optInt1.getAsInt()); //0
      //System.out.println(optInt2.getAsInt()); //NoSuchElementException
        System.out.println("optInt1="+optInt1); //OptionalInt[0]
        System.out.println("optInt2="+optInt2); //OptionalInt.empty
        System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2)); //false -> 0이랑 비어있는것은 다른것이다.
 
        Optional<String> opt = Optional.ofNullable(null); //null을 저장한다.
        Optional<String> opt2 = Optional.empty();   //빈 객체를 생성
        System.out.println("opt="+opt); //Optional.empty
        System.out.println("opt2="+opt2); //Optional.empty
        System.out.println("opt.equals(opt2) ?"+opt.equals(opt2)); //true -> null이랑 비어있는것은 같은것이다.
 
        int result3 = optStrToInt(Optional.of("123"),0);
        int result4 = optStrToInt(Optional.of(""),0);
 
        System.out.println("result3="+result3);  //123
        System.out.println("result4="+result4); //0
    }
 
    static int optStrToInt(Optional<String> optStr, int defaulValue){
        try {
            return optStr.map(Integer::parseInt).get();
        }catch (Exception e){
            return defaulValue;
        }
    }
}
 
cs

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

+ Recent posts