ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다3편) stream + Optional 기능(feat. orElse VS orElseGet)
    Java 2023. 9. 22. 13:36

    Optional의 문법적 개념과 어떤 상황에 써야하는지 배웠지만 잘 감이 오지 않았다.

    그러다가 stream에서 자주 사용되는것을 보고 감을 잡기 시작했다. 

     

    stream을 사용할때 반환값으로 Optional을 갖는 메서드들이 있다.  findAny(), findFirst()...

     

    우선 Optional에대해 공부하자.


     

    - Optional 객체를 생성하는 static함수 3가지

    - empty()

    Optional은 Wrapper 클래스이기 때문에 값이 없을 수도 있는데, 이때는 Optional.empty()로 생성할 수 있다.

    public static<T> Optional<T> empty() {
      @SuppressWarnings("unchecked")
      Optional<T> t = (Optional<T>) EMPTY;
      return t;
    }

    empty()로 인해 생성된 Optional 객체는 다음 그림과 같은 상태를 가진다.

     

    - of()

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    of 는 value가 null이 아닌경우 사용한다. null이 들어오면 NPE를 터트린다.

     

    - ofNullable()

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    ofNullable()은 null이 들어오면 empty()처리해서 null을 안전하게 처리할 수 있다. 


    - Optional을 활용하는 메서드 orElse()와 orElseGet()

    public T orElse(T other) {
        return value != null ? value : other;
    }
    
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    두 메서드 모두 T 타입의 파라미터를 받아 null일때 T 타입을 반환한다.

    즉 역할은 null일때 대체값을 반환하기 위한 메서드라고 볼 수 있다. 

     

    두 메서드의 큰 차이점 두가지가있다.

     

    1.  orElse()의 파라미터는 일반 래퍼클래스라면, orElseGet()은 파라미터가 함수형 인터페이스다.

    2.  orElse의 파라미터는 null 이든말든 항상 호출되지만, orElseGet()의 파라미터는 null일때만 호출된다.

     

    2번의 이유는 1번에 있다. 

     

    orElse()는 다음과 같이 실행된다.
    public String print(){
     return "test";
    }
    String name = optional.orElse(print());  
    orElse의 프로세스는 orElse()가 실행될때 안에 있는 print()가 먼저 실행된다.
    그리고 optional에 대한 orElse()가 실행된다.
    반면 orElseGet()를 보자

    public String print(){
       return "test";
    }
    String optional = "test";
    String name = optional.orElseGet(this::print);  

    orElseGet()의 프로세스는 다음과 같다. 

    orElseGet()이 호출될때,
    optional 객체에 대한 orElseGet()이 먼저 실행된 뒤에(optional null check) Null 일때만 
    orElseGet()안에 있는 print메서드가  실행될 수 있다.

    orElse와 orElseGet의 파라미터를 다루는 방식에 차이가 있기 때문에 이러한 결과가 도출되었다.

     


    Stream으로 Optional 활용하기!

     

    for+if 문과 Stream+Optional 활용문을 비교해서 보자

    int []IntegerArray = new int[]{1,2,3,4,5,6,7,8,9};
    
    List<Integer>list = Arrays.stream(IntegerArray).boxed().toList();
    Integer findNumber = null;
    for (int i=0;i<list.size;i++){
    	Integer k = list.get(i);
        if (k==1234)
        	findNumber = i;
     	}
        
    if (findNumber!=null) throw new RuntimeException;
    int []IntegerArray = new int[]{1,2,3,4,5,6,7,8,9};
    
    List<Integer>list = Arrays.stream(IntegerArray).boxed().toList();
    
    Integer findNumber = list.stream()
    			.filter(Integer -> {
    			if (Integer.equals(1234)
    				return true;
    			return false;
    			})
    			.findAny() // findAny()가 Optional을 반환함
    			.orElseThrow();

    만약 1234가 나온다면 예외를 터트리는 문장이다. 

     

    for문+if문의 경우는 코드도 구분되어있어 서로 어떤 관계인지 명확히 알 수 없다.

     

    반면 stream을 활용했을때는 어떤 로직을 사용하는지 한번에 알 수 있다.

     

    이제 stream에서 optional을 활용할 수 있는 메서드에 대해 알아보자.

     


    Optional 로 반환하는 stream 메서드

     

     - findAny()

    findFirst()는 filter 조건에 일치하는 element 1개를 Optional로 리턴한다. 조건에 일치하는 요소가 없다면 empty가 리턴된다.

     

    - findFirst()

    조건에 일치하는 요소들중 제일 앞에 있는 stream을 return한다.

     

    사전적 정의만 봐서는 두가지가 같은것 같은데?

    차이점

    stream을 직렬로 처리할때 findAny와 findFirst에는 차이점이 없다.
    차이점은 병렬로 처리할때 있다.

    findFirst()는 여러 요소가 조건에 부합해도 stream의 순서를 고려해서 가장 앞에 있는 요소를 return한다.
    반면 findAny()는 Multi Thread에서 stream을 처리할때 가장 먼저 찾은 요소를 return한다. 즉, 뒤쪽에 있는 요소가 return될수도 있다는 뜻이다. 

     

    병령 코드를 구현하며 알아보자

     

    List<String> elements = Arrays.asList("a", "a1", "b", "b1", "c", "c1");
    
    Optional<String> firstElement = elements.stream().parallel()
            .filter(s -> s.startsWith("b")).findFirst();
    
    System.out.println("findFirst: " + firstElement.get());

    findFirst()는 항상 b를 return한다.

     

    List<String> elements = Arrays.asList("a", "a1", "b", "b1", "c", "c1");
    
    Optional<String> anyElement = elements.stream().parallel()
            .filter(s -> s.startsWith("b")).findAny();
    
    System.out.println("findAny: " + anyElement.get());

    findAny()는 b1또는 b를 return한다. 즉 실행할 때마다 리턴값이 달라질 수 있다는 것이다.

     


    Optional객체가 갖는 메서드(stream에서 자주 활용)

    - ifPresentOrElse()

    Optional 객체가 2개의 파라미터를 받는데,

    첫번째는 유효한 객체를 받을때 실행하고, 두번째는 유효한 객체를 받지 못했을때 실행한다.

     

    - orElseThrow

     

    최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 예외 공급자 함수를 통해 예외를 발생시킨다.


    Reference 

     

    - https://cfdf.tistory.com/34

    - https://codechacha.com/ko/java8-stream-difference-findany-findfirst/

    - https://jdm.kr/blog/234

Designed by Tistory.