Language/Java

[자바 스터디 15주차 번외] 스트림 & Optional

소프 2021. 3. 7.

Stream 소개

  • 데이터를 담고 있는 저장소(컬렉션)이 아니다.
  • Functional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다.
    • Functional 하다.
    • 결과가 또 다른 stream이 되는 것이지, 전달받은 데이터 자체가 변경되는 것이 아니다.
  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다.
    • 컨베이어 벨트에 항목이 한번 지나가는 것이라고 보면된다.(한번 지나면 끝)
  • 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
  • 중개 오퍼레이션은 근본적으로 lazy 하다.
    • stream에 사용하는 것은 2개로 나눌 수 있다. (중개, 종료)
    • 중개 오퍼레이션은 lazy : lazy하다는 말은 종료 오퍼레이션이 나오기 전까지는 실행하지 않는 것을 뜻한다. 즉, 결과가 필요하기 전까지 연산의 시점을 최대한 늦추는 것을 뜻한다.
    • 중개형 오퍼레이션은 종료 오퍼레이션이 오기 전까지 실행되지 않는다.
package me.ssonsh.java8to11;

import java.util.*;

public class App {

    public static void main(String[] args){
        List<String> names = new ArrayList<>();
        names.add("sson");
        names.add("ssh");
        names.add("sunghyun");
        names.add("son");
        names.add("son sung hyun");

        names.stream().map(x -> {
        	System.out.println(x);
            return x.toUpperCase(Locale.ROOT);
        });
        
        System.out.println("=====');
        names.forEach(System.out.println);

    }
}
///
결과
==========
sson
ssh
sunghyun
son
son sung hyun

.map() 바디에 선언한 System.out.println은 찍히지 않는 것을 볼 수 있다.

 

즉, 종료형 오퍼레이션이 반드시 와야하며,

종료형 오퍼레이션이 오지 않으면 중계형 오퍼레이터는 의미가 없다.(실행 x)

names.stream().map(x -> {
	System.out.println(x);
    return x.toUpperCase(Locale.ROOT);
})
.forEach(System.out::println);

System.out.println("==========");
names.forEach(System.out::println);

/// 
결과

sson
SSON
ssh
SSH
sunghyun
SUNGHYUN
son
SON
son sung hyun
SON SUNG HYUN
==========
sson
ssh
sunghyun
son
son sung hyun
  • 손쉽게 병렬 처리 할 수 있다.
    • 병렬처리를 하는 것이 모두 빠른 것이 아니다. 더 느릴 수 있다.
    • Thread를 만들어서 Thread별로 처리하고 수집하는 일련의 과정이 발생된다.
    • 데이터가 정말 방대하게 큰 경우 유용하게 사용될 수 있으나, 그게 아니라면 stream 권장
List<String> collect = names.parallelStream()
							.map(String::toUpperCase)
                            .collect(Collectors.toList());

collect.forEach(System.out::println);

 

-> 처리되는 Thread 확인

List<String> collect = 
	names.parallelStream().map(x -> {
    	System.out.println(x + " " + Thread.currentThread().getName());
        return x.toUpperCase();
    })
    .collect(Collectors.toList());
    
collect.forEach(System.out::println);

/////
sunghyun main
ssh ForkJoinPool.commonPool-worker-1
son ForkJoinPool.commonPool-worker-4
sson ForkJoinPool.commonPool-worker-3
son sung hyun ForkJoinPool.commonPool-worker-2
SSON
SSH
SUNGHYUN
SON
SON SUNG HYUN

-> parallelStream()이 아니라 stream()을 사용하게 되면?

sson main
ssh main
sunghyun main
son main
son sung hyun main
SSON
SSH
SUNGHYUN
SON
SON SUNG HYUN

Stream Pipline

  • 0 또는 다수의 중개 오퍼레이션과 한 개의 종료 오퍼레이션으로 구성한다.
  • 스트림의 데이터 소스는 오직 종료 오퍼레이션을 실행할 때만 처리한다.

중개 오퍼레이션

  • Stream을 리턴한다.
  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다.
    • 대부분 Stateless지만 distinct나 sorted처럼 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.

예시

  • filter : 필터링, Predicate<T>를 받음
List<Integer> list = new LinkedList<>();
list.add(10);
list.add(20);
list.stream().filter( (v) -> v > 10).forEach.System.out::println); 
// 10이상 리스트 반환

list.stream().forEach(System.out::println); 
// 10 20 출력 : 원래 리스트엔 변환이 없다.
  • map : Function<T, R> 받음
package me.ssonsh.java8to11.streamapi;

public class OnlineClass {

    private Integer id;
    private String title;
    private boolean closed;
    
    ...생성자, getter, setter
    
}

		List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));
        springClasses.add(new OnlineClass(5, "rest api development", false));

List<String> classesByTitle =
        springClasses.stream()
                    .map(OnlineClass::getTitle)
                    .collect(Collectors.toList());
                    
classesByTitle.forEach(x -> System.out.println("    > " + x));
  • flatMap : 모든 항목을 풀어내는것
    • 두 수업 목록에 들어있는 모든 수업 아이디 출력
    • 자세한 내용은 바로 아래 내용 참고
 		List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        List<OnlineClass> javaClasses = new ArrayList<>();
        javaClasses.add(new OnlineClass(6, "The Java, Test", true));
        javaClasses.add(new OnlineClass(7, "The Java, Code manipulation", true));
        javaClasses.add(new OnlineClass(8, "The Java, 8 to 11", false));


        List<List<OnlineClass>> ssonEvents = new ArrayList<>();
        ssonEvents.add(springClasses);
        ssonEvents.add(javaClasses);

System.out.println("두 수업 목록에 들어있는 모든 수업 아이디 출력");
//Collection::Stream -> Colleciton의 자손인 List와 Set을 구현한 컬렉션 클래스들은 
					 // 모두 이 메서드로 스트림을 생성할 수 있다.
ssovEvents.stream().flatMap(Collection::stream) 
					.forEach(oc -> System.out.println(oc.getId()));

※ flatMap에 더 알아보자.

출처 : madplay.github.io/post/difference-between-map-and-flatmap-methods-in-java

 

https://kchanguk.tistory.com/56

flatMap 메서드는  Array나 Object로 감싸져 있는 모든 원소를 단일 원소 스트림으로 반환할 수 있습니다. 아래 코드는 2차원 배열에서 문자열의 길이가 3보다 큰 문자열을 출력하는 코드입니다.

String[][] namesArray = new String[][]{
	{"kim", "taeng"}, {"mad", "play"},
    {"kim", "mad"}, {"taeng", "play"}};
    
Set<String> namesWithFlatMap = Arrays.stream(namesArray)
			.flatMap(innerArray -> Arrays.stream(innerArray))
            .filter(name -> name.length() > 3)
            .collect(Collectors.toSet());
            
// play, taeng 출력
namesWithFlatMap.forEach(System.out::println);

flatMap의 결과로 단일 원소 스트림을 반환하기 때문에 filter 메서드를 바로 체이닝하여 사용할 수 있습니다. 초기에 생성된 스트림이 배열인 경우엥 매우 유용합니다.

 

 

예시2

String[][] namesArray = new String[][]{
        {"kim", "taeng"}, {"mad", "play"}};
        
//flatMap
Arrays.stream(namesArray)
		.flatMap(inner -> Arrays.stream(inner))
        .filter(name -> name.equals("taeng"))
        .forEach(System.out::println);
        
// map
Arrays.stream(namesArray)
		.map(inner -> Arrays.stream(inner))
        .forEach(names -> names.filter(name -> name.equals("taeng"))
            .forEach(System.out::println));

단순히 출력하는 코드만으로 비교해보면 차이는 조금 더 명확해집니다. flatMap은 결과를 스트림으로 반환하기 때문에 flatMap의 결과를 가지고 바로 forEach 메소드를 체이닝하여 모든 요소를 출력할 수 있습니다.

 

반면에 map의 경우에는 단일 요소로 리턴되기 때문에 map의 결과를 가지고 forEach 메서드로 루프를 진행한 후 그 내부에서 다시 한번 forEach 메소드를 체이닝하여 사용해야 합니다. 이후는 동일하게 메서드 참조 형태로 표준 출력 합니다.

 

예시3

조금더 간단한 로직. 단순히 2차원 배열의 모든 요소를 출력하는 코드

String[][] namesArray = new String[][]{
        {"kim", "taeng"}, {"mad", "play"}};
        
// flatMap
Arrays.stream(namesArray)
	.flatMap(inner -> Arrays.stream(inner))
    .forEach(System.out::println);
    
// map
Arrays.stream(namesArray)
	.map(inner -> Arrays.stream(inner))
    .forEach(names -> names.forEach(System.out::println));
  • limit : 개수제한, skip : 개수만큼 넘김
System.out.println("10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만");
	Stream.iterate(10, i -> i + 1)
    		.skip(10)
            .limit(10)
            .forEach(System.out::println);
  • sorted : 정렬하기
List<Integer> list = new LinkedList<>();
list.add(20);
list.add(10);
list.stream().sorted().forEach(System.out::println);
//Comparator 사용 예
list.stream().sorted( (v1, v2) -> v2 - v1).forEach(System.out::println);
  • distinct : 중복제거
List<Integer> list = new LinkedList<>();
list.add(10);
list.add(10);
list.stream().distinct().forEach(System.out::println);

종료 오퍼레이션

  • Stream을 리턴하지 않는다.

예시

  • findFirst/findAny : 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환
List<Integer> list = new LinkedList<>();
list.add(10);
list.add(10);
list.stream().filter(v -> v > 10).findAny();
  • findFirst : 스트림의 순서상 가장 첫번째 있는 것을 리턴
  • findAny : 순서와 관계없이 먼저 찾아지는 객체를 리턴
    • 병렬 처리시(parallel()) 순서대로 탐색하지 않기 때문에 아래 예시에서 findAny는 b1 또는 b 중에 가장 먼저 찾은 것을 리턴한다.
List<String> elements =
        Arrays.asList("a", "a1", "b", "b1", "c", "c1");
firstElement = elements.stream().parallel()
        .filter(s -> s.startsWith("b")).findFirst();
anyElement = elements.stream().parallel()
        .filter(s -> s.startsWith("b")).findAny();
        
        
// 결과
b
b1
  • allMatch/anyMatch/nonMatach/ : 세 메소드 모두 인수로 Predicate 객체를 전달받으며, 요소의 검사 결과는 boolean값으로 반환한다.
    • anyMatch : 해당 스트림의 일부 요소가 특정 조건을 만족할 경우 true를 반환
    • allMatch() : 해당 스트림의 모든 요소가 특정 조건을 만족할 경우 true를 반환
    • noneMatch() : 해당 스트림의 모든 요소가 특정 조건을 만조하지 않을 경우 true를 반환
List<Integer> list = new LinkedList<>();
list.add(10);
list.add(10);
list.stream().allMatch(v -> v == 10);
  • forEach : 스트림 요소들 훑기
  • min, max, count, sum

요소의 수집

collect() 메소드로 인수로 전달되는 Collectors 객체에 구현된 방법대로 스트림의 요소를 수집합니다.

또한, Collectors 클래스에는 미리 정의된 다양한 방법이 클래스 메소드로 정의되어 있습니다.

이 외에도 사용자가 직접 Collector 인터페이스를 구현하여 자신만의 수집 방법을 정의할 수도 있습니다.

 

스트림의 수집 용도별 사용할 수 있는 Collectors 메소드는 다음과 같습니다.

 

1. 스트림을 배열이나 컬렉션으로 변환 : toArray(), toCollections(), toList(), toSet(), toMap()

2. 요소의 통계와 연산 메소드와 같은 동작을 수행 : counting(), maxBy(), minBy(), summingInt(), averagingInt() 등

3. 요소의 소모와 같은 동작을 수행 : reducing(), joining()

4. 요소의 그룹화와 분할 : groupingBy(), partitioningBy()

  • Reduction : Stream의 데이터를 변환하지 않고, 더하거나 빼는 등의 연산을 수행하여 하나의 값으로 만든다면 reduce를 사용할 수 있습니다.
  • 예를 들면, 아래 코드는 1+2+...+10의 합을 출력해줍니다.
Stream<Integer> numbers = Stream.of(1,2,3,4,5,6,7,8,9,10);
Optional<Integer> sum = numbers.reduce( (x,y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

//결과
sum: 55

reduce()의 param으로 (total, n) -> total + n가 전달되고, reduce는 a1, a2, a3, a4...의 stream을 a1 + a2 + a3 + a4 ...로 연산을 수행합ㄴ니다. 첫번째로 total=a1, n=a2가 되고, 두번째로 total=(a1+a2), n=a3가 됩니다.

 

함수를 정의하지 않고 Integer::sum을 사용해도 위와 동일한 결과를 출력합니다.

Stream<Integer> numbers2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum2 = numbers2.reduce(Integer::sum);
sum2.ifPresent(s -> System.out.println("sum2: " + s));

 

초기값 있는 Reduction

위와 동일한 동작을 하지만 초기값을 지정해줄 수 있습니다. 첫번째 param으로 초기값을 넘겨주면 됩니다. 초기값이 없는 위와 유사하게 '10 + 1 + 2 + ... 1 + 0'으로 연산됩니다.

Stream<Integer> numbers3 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum3 = numbers3.reduce(10, (total, n) -> total + n);
System.out.println("sum3: " + sum3);

//결과
sum3 : 65

 

병렬처리 & Reduction

parallel()을 사용하면 병렬처리로 연산을 수행할 수 있습니다. 순차적으로 연산을 수행하지 않고 여러개의 작업을 병렬로 처리합니다. (1 + 2) + (3 + 4) + ... + (9 + 10)처럼 병렬적으로 처리됩니다.

Stream<Integer> numbers4 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum4 = numbers4.parallel().reduce(0, (total, n) -> total + n);
System.out.println("sum4: " + sum4);

//결과
sum4: 55

하지만 -(마이너스) 연산인 경우 병렬처리는 순차처리와 결과가 다릅니다. 아래 코드를 보면 결과가 -55가 아니라 -5가 됩니다. (1 - 2) - (3 - 4) - ... - (9 - 10)처럼 연산의 순서가 어떻게 되냐에 따라서 결과가 순차처리와 다르게 됩니다.

Stream<Integer> numbers5 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum5 = numbers5.parallel().reduce(0, (total, n) -> total - n);
System.out.println("sum5: " + sum5);

//결과
sum5: -5

 

순서가 있는 병렬처리 Reduction

병렬처리에서 연산 순서에 따라 발생하는 문제를 해결하기 위해 다음과 같이 다른 규칙을 추가할 수 있습니다. param으로 (total1, total2) -> total1 + total2를 추가하였는데 병렬처리된 결과의 관계를 나타냅니다. 첫번째 연산과 두번째 연산은 합해야 한다는 규칙을 정의하여, 연산결과과 순차처리와 동일하게 됩니다.

Stream<Integer> numbers6 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum6 = numbers6.reduce(0,
			(total, n) -> total - n,
            (total, total2) -> total1 + total2);
System.out.println("sum6: " + sum6);

//결과
sum6: -55

 

groupingBy

컬렉션의 요소들을 그룹핑해서 Map객체를 생성하는 기능, groupingBy()는 Map객체를 리턴해줍니다.

문자열 리스트 -> map으로 바꾸는 로직

groupingBy 쓰지 않았을 때

public Map<String, Long> toMap<List<String> strings) {
	Map<String, Long> mpa = new HashMap<>(); //<문자열, 문자열빈도>
    for(String element : strings) {
    	if(map.containsKey(element)){ //
        	map.put(element, map.get(element) + 1);
        } else{
			map.put(element, 1);
        }
    }
    return map;
}

groupingBy 사용했을 때

public Map<String, Long> toMap(List<String> strings) {
	return strings.stream()
    	.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      //.forEach( (key, value) -> System.out.println(key + " / " + value));
}

Function.identity()는 x -> x와 같다.

 

Primitive 타입 타입이 아닌 객체 타입 예시1

public class Coder {
  private String name;
  private String language;
  private int careerYear;
  private int wage;
  ... constructor, getter
}
public Map<String, List<Coder>> groupByLanguage(List<Coder> coders) {
  return coders.stream()
    .collect(Collectors.groupingBy(Coder::getLanguage(), Collectors.toList()));
}

 

 

 

예시2

List<tempVO> tempList = new ArrayList<>();
tempList.add(new tempVO("KRW", 1000));
tempList.add(new tempVO("KRW", 2000));
tempList.add(new tempVO("USD", 100));
tempList.add(new tempVO("USD", 200));
tempList.add(new tempVO("JPY", 50));


Map<String, Long> map1 = tempList.stream().collect(
		Collectors.groupingBy(tempVO::getCurrency, Collectors.summingLong(tempVO::getAmount))
);

class tempVO{
    String Currency;
    int amount;
    
// 결과
KRW / 3000
JPY / 50
USD / 300

summingLong, summingInt 등 은 각 리스트에 선택된 값들을 합한 값으로 리턴한다.

 

출처

(1) 스트림

www.tcpschool.com/java/java_stream_terminal

 

(2) reduce

codechacha.com/ko/java8-stream-reduction/

 

(3) groupingBy

andole98.github.io/java/stream-groupingby/#

 

Optional 소개

 

자바 프로그래밍에서 NullPointerExceptin을 종종 보게 되는 이유

  • null을 리턴하기 때문
  • null 체크를 깜빡했기 때문

메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택할 수 있는 방법

  • 예외를 던진다
    • 비싸다. 스택 트레이스를 찍어두기 때문
  • null을 리턴한다.
    • 비용 문제는 없지만, 코드를 사용하는 클라이언트가 주의해야 한다.
  • Optional을 리턴한다.
    • 클라이언트에게 명시적으로 빈값일 수도 있다는 것을 알려주고, 빈 값에 대한 처리를 강제한다.

 

Optional 

  • 오직 값 한개가 들어있을 수도, 없을 수도 있는 컨테이너

주의할 것

  • 리턴값으로만 쓰기를 권장
    • 메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
    • 정말 리턴할 것이 없다면 Optional.empty();를 반환하자.
  • 프리미티브 타입용 Optional은 따로 있다.
    • OptionalInt, OptionalLong, ...
    • Optional.of(10)으로 사용할 수 있지만 성능상 좋지 않다.
      • Boxing, UnBoxing이 일어나는 것 자체가 성능에 저하가 된다.
      • OptionalInt을 사용하자.
  • Collection, Map, Stream, Array, Optional은 Optional로 감싸지 않는다.
    • 그 자체로 비어 있음을 알 수 있음으로 Optional로 감쌀 필요가 없다.
    • 감싸게 된다는 것 자체는 성능 저하를 발생할 수 있고, 의미 없는 코드이다.

 

Optional

비어있는 값이 전달될 수 있다는 것을 클라이언트단에게 알려주기 위한 방법으로 Optional을 사용할 수 있다.

  • 문법적으로 Optional 사용의 제한은 없지만,
  • return 타입으로 사용하는 것 만이 권장사항.

why?

-> 파라미터를 호출할 때 Optional을 사용하는 경우, 결국 넘어온 파라미터를 null check를 해야 한다.(의도하지 않았더라도) 즉 의미가 없다는 것이다.

-> 사용자의 실수는 언제든지 발생할 수 있는 케이스가 열려 있다.

public Optional<Progress> getProgress(){
	// 객체가 null일 수 있다면, ofNullable로 Optional을 사용한다.
    // of는 객체가 null이 아닐 때 사용하도록 한다.
    return Optional.ofNullable(progress);
}

 

Optional API

 

Optional 만들기

  • Optional.of() : null이 아닌 객체를 담고 있는 Optional 객체를 생성. null이 넘어올 경우 NPE를 던진다.
  • Optional.ofNullable() : null인지 아닌지 확신할 수 없는 객체를 담고 있는 Optional 객체를 생성.
  • Optional.empty() : 비어있는 Optional 객체를 리턴

Optional에 값 있는지 없는지 확인하기

  • isPresent()
  • isEmpty() -> Java11부터 제공

 

Optional에 있는 값 가져오기

  • get() : 비추!!
  • 만약 비어있는 Optional에서 무언가를 꺼내면? : NoSuchElementException을 던진다.
    • 예외를 발생시키므로 get()대신, orElset()/orElsetGet()/orElseThrow()를 사용하자.
// 안 좋음
Optional<Member> member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    return null;
}

// 좋음
Optional<Member> member = ...;
return member.orElse(null);



// 안 좋음
Optional<Member> member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    throw new NoSuchElementException();
}

// 좋음
Optional<Member> member = ...;
return member.orElseThrow(() -> new NoSuchElementException());

NoSuchElementException

Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하라.

  • ifPresent(Consumer) : 비추 ( != null 처리랑 똑같음)
Optional<OnlineClassOp> jpa = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("jpa"))
			          .findFirst();

jpa.ifPresent(oc -> System.out.println(oc.getTitle()));

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 리턴하라.

  • orElse(T)
  • Optional에 값이 있든 없는 T부분은 무조껀 실행이 된다.
  • Optional에 값이 있으면 orElse()의 인자로서 실행된 값이 무시되고 버려진다.
  • 따라서, T부분은 새 객체 생성이나 새로운 연산을 유발하지 않고 이미 생성되었거나, 이미 계산된 값일 때만 사용해야 한다.

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 하라.

  • orElseGet(Supplier)
  • Supplier가 새로운 객체를 생성하거나 새로운 연산을 수행하는 경우 사용한다.
  • Supplier는 Optional에 값이 없을 때만 실행된다. 따라서 Optional에 값이 없을때만 새 객체를 생성하거나 새 연산을 수행하므로 불필요한 오버헤드가 없다.
// 안 좋음
Optional<Member> member = ...;
return member.orElse(new Member());  // member에 값이 있든 없든 new Member()는 무조건 실행됨

// 좋음
Optional<Member> member = ...;
return member.orElseGet(Member::new);  // member에 값이 없을 때만 new Member()가 실행됨

// 좋음
Member EMPTY_MEMBER = new Member();
...
Optional<Member> member = ...;
return member.orElse(EMPTY_MEMBER);  // 이미 생성됐거나 계산된 값은 orElse()를 사용해도 무방

 

Optional에 값이 있으면 가져오고 없는 경우에 에러를 발생시켜라

  • orElseThrow()
// supplier -> 메소드 레퍼런스 / 람다 표현식
jpa.orElseThrow(IllegalStateException::new);
jpa.orElseThrow(() -> {return new IllegalStateException()});

 

Optional에 들어있는 값 걸러내기

  • Optional filter(Predicate)
List<OnlineClassOp> springClasses = new ArrayList<>();
springClasses.add(new OnlineClassOp(1, "spring boot", true));
springClasses.add(new OnlineClassOp(2, "spring data jpa", true));
springClasses.add(new OnlineClassOp(3, "spring mvc", false));
springClasses.add(new OnlineClassOp(4, "spring core", false));
springClasses.add(new OnlineClassOp(5, "rest api development", false));

Optional<OnlineClassOp> jpa = springClasses.stream()
		.filter(oc -> oc.getTitme().startsWith("spring"))
        .findfirst();
        
Optional<OnlineClassOp> onlineClassOp =
	jpa.filter(oc -> oc.isClosed());

onlineClassOp.ifPresent(oc -> System.out.println(oc.getTitme()));

 

Optional에 들어있는 값 변환하기

  • Optional map(Function)
Optional<Integer> ocId = jpa.map(OnlineClassOp::getId);
ocId.ifPresent(System.out::println);

-> 만약 map으로 만드려고 하는 객체가 Optional인 경우 복잡해질 수 있다.

    map 자체도 Optional이고, 이를 담고 있는 정보도 Optional

Optional<Optional<Progress>> progress = jpa.map(OnlineClassOp::getProgress);
Optional<Progress> progress1 = progress.orElseThrow(IllegalStateException::new);
progress1.ifPresent(Progress::getStudyDuration);

이 때 조금 더 수월하게 사용할 수 있는 것이 "flatMap"

  • Optional flatMap(Function) => Optional안에 들어있는 인스턴스가 Optional인 경우 사용하면 편리.
  • map으로 반환하는 결과가 Optinal인 경우, Optional를 제거한 이후 정보로 반환
Optional<Progress> progress = jpa.flatMap(OnlineClassOp::getProgress);
progress.ifPresent(pg -> System.out.println(pg.getStudyDuration()));

<> Stream에서의 flatMap과는 다름에 주의할 것.

 

출처 Optional

www.daleseo.com/java8-optional-after/

homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/

 

'Language > Java' 카테고리의 다른 글

early return이란?  (0) 2021.03.13
[자바 스터디 15주차] 람다식  (0) 2021.03.05
[자바 스터디 14주차] 제네릭  (0) 2021.02.27
[자바 스터디 13주차] IO  (0) 2021.02.20
[자바 스터디 12주차] 어노테이션  (0) 2021.02.20

댓글