이전에 람다(Lambda)를 정리하면서 Java 1.8 부터 제공하는 람다를 활용하는 스트림(Stream) API에 대해서도 정리하고자 합니다.
기존의 Java 에서는 Collection 데이터(List, Map 등)를 처리할 때 for, while 문 등 특정 조건에 따라 요소 하나씩을 꺼내어 처리하면서 복잡한 과정을 거쳐야 했습니다.
아직도 람다 방식이 익숙치 않고 스트림 API도 종종 사용하지만 올바르게 활용하지는 못하고 있는 것 같아 정리하여 기록해보고자 합니다.
1. Stream 이란?
- 데이터를 추상화하고, 처리하는데 자주 사용되는 함수들을 정의한다.
데이터 추상화 : 데이터의 종류에 상관 없이 같은 방식으로 데이터를 처리
- 데이터의 흐름 -> 배열 또는 컬렉션에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다.
- 스트림 API 는 다음과 같이 3단계에 걸쳐서 동작한다.
- 스트림의 생성
- 스트림의 중간 연산(스트림의 변환)
- 스트림의 최종 연산(스트림의 사용)
2. Stream 특징
- 원본의 데이터를 변경하지 않음.
- 일회용.
- 내부 반복으로 작업을 처리.
- 지연 연산.(최종 연산이 수행되기 전까지 중간 연산이 수행되지 않음)
2-1. 원본의 데이터를 변경하지 않음
- 원본의 데이터를 조회하여 원본의 데이터가 아닌 별도의 요소들로 Stream 을 생성한다.
- 원본의 데이터를 읽기만 할 뿐, 정렬이나 필터링 등의 작업은 별도의 Stream 요소들에서 처리한다.
List<Object> streamTest = testList.stream().sorted().collect(collectors.toList());
2-2. 일회용
- 한번 사용이 끝나면 재사용이 불가능하다.
- 필요시 다시 Stream 을 생성하여 사용해야 한다.(iterator 와 동일)
String[] arr = {"A", "C", "B", "E", "D"};
// Stream(스트림) 생성
Stream<String> test = Arrays.asList(arr).stream();
// Stream 사용
test.sorted().forEach(System.out::println);
// 실행시 에러 발생
// java.lang.IllegalStateException: stream has already been operated upon or closed
System.out.println(test.count());
2-3. 내부 반복으로 작업 처리
- 기존 반복문을 사용하기 위해서는 for, while 등과 같은 문법을 사용했지만, 스트림에서는 메소드 내부에 숨기고 있어 간결한 코드 작성이 가능하다.
String[] arr = {"A", "C", "B", "E", "D"};
List<String> test = Arrays.asList(arr);
// Stream 사용
test.stream()
.forEach(System.out::println);
// Stream 사용 X
for(int i = 0; i < test.legnth; i ++){
System.out.println(test.get(i));
}
3. Stream API 연산 종류
3-1. 생성하기
- 스트림 객체를 생성하는 단계이다.
- 스트림은 재사용이 불가능하므로, 닫히면 재생성해야 한다.
3-2. 가공하기(중간연산)
- 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산이다.
- 연산 결과를 스트림으로 다시 반환하기 때문에 연속해서 중간 연산이 가능하다.
distinct() | 중복 요소 제거 |
sorted() | 기존 요소 정렬 |
filter() | 조건에 충족하는 데이터만을 정제하여 생성 |
peek() | Stream 에 영향을 주지 않고 특정 연산을 수행 |
map() | 기존 요소들을 변환하여 새로운 요소로 생성 |
mapToInt mapToObj |
일반적인 Stream 객체를 원시 Stream 으로 바꾸거나, 반대로 하는 작업 |
3-3. 결과만들기(최종연산)
- 가공된 데이터로부터 원하는 데이터를 만들기 위한 최종 연산이다.
max(), sum(), count() ... | 요소들을 대상으로 최댓값, 총합, 갯수 등을 반환 |
collect() | 요소들을 List 나 Set, Map 등 다른 종류의 결과로 반환 |
anyMatch(), allMatch() ... | 요소들을 특정한 조건을 충족하는지 검사 |
forEach() | 요소들을 대상으로 특정한 연산을 수행 |
4. Example
String[] arr = {"A", "C", "B", "E", "D", "1f", "1g"};
Arrays.asList(arr)
.stream() // 생성하기(Stream 생성하기)
.filter(f -> f.startsWith("1")) // 가공하기(중간연산, filter -> 1f, 1g 를 걸러냄)
.map(m -> m.substring(1,2).toUpperCase()) // 가공하기(중간연산, map -> 1을 자르고 대문자로 변환하여 저장)
.sorted() // 가공하기(중간연산, sorted -> 정렬)
.collect(Collectors.toList()) // 가공하기(중간연산, collect -> List 로 변환)
.forEach(System.out::println); // 결과만들기(최종연산, 반복하여 출력)
// 결과 : F G
참고
'BackEnd > Java' 카테고리의 다른 글
[Java] 어노테이션(Annotation) 정리 (0) | 2022.09.05 |
---|---|
[Java] @SneakyThrows 란?(Lombok 어노테이션) (4) | 2022.08.20 |
[Java] MDC 를 사용한 로그(Log)추적하기 (0) | 2022.07.30 |
[Java] Lambda 정리 (0) | 2022.07.16 |
[Java] Lombok 정리 (0) | 2022.07.06 |