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
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 |