Java 8에서 큰 변경점 중 하나인 Stream API를 공부해 보자.
(스크립트 언어를 주로 쓰다 Java로 넘어오니 답답한 부분들이 많았는데 조금이나마 해소되었다 :D)
# Stream 이란?
Java에서는 많은 양의 데이터를 저장하기 위해 배열이나 컬렉션을 사용한다.
하지만, 이렇게 저장된 데이터에 접근하려면 반복문(for, while)이나 반복자(iterator)를 사용해야만 했는데,
이는 너무 장황할뿐더러 재사용이 거의 불가능하다.
또한, 내가 Java를 처음 공부하며 킹 받았던 부분인데,
각 컬렉션 클래스마다 같은 기능의 메서드가 중복 정의 되어있다.
List를 정렬할 때는 Collections.sort()를 쓰고, 배열을 정렬할 때는 Arrays.sort() 쓰고?!
이러한 문제점들을 해결하기 위해 Java SE 8부터 Stream API가 도입되었다. (2014.03.18 출시)
해결된 문제점 : 장황함, 재사용 불가, 중복 정의 된 기능
이 Stream API의 특징은 다음과 같다.
- 컬렉션과 다르게 내부 반복(internal iteration)을 통해 작업함. (forEach() 안에 람다식 사용)
- 재사용이 가능한 컬렉션과 달리 한 번만 사용 가능함. (최종 연산 후 스트림이 닫혀 다시 사용 불가)
- 원본 데이터를 변경하지 않음. (데이터 소스로 부터 오직 읽기만 함, 필요하면 컬렉션이나 배열에 반환 가능)
- 지연(lazy)된 연산을 통해 성능을 최적화함. (sort()나 distinct() 같은 중간 연산을 최종 연산 전까지 수행하지 않음)
- parallelStream() 메서드를 통해 병렬 처리를 지원함. (기본은 sequential()이라 호출 안 해도 됨)
그리고 이 Stream 연산은 크게 중간 연산과, 최종 연산 두가지로 구분된다.
- 중간 연산 : 연산 결과가 스트림인 연산. (연속해서 중간 연산 가능)
- distinct : 중복 제거
- filter : 조건에 안 맞는 요소 필터링
- limit : 요소 길이 제한
- skip : 일부 건너뛰기
- peek : 요소에 작업
- sorted : 정렬
- map, mapToDouble, mapToInt, mapToLong : 스트림 요소 변환
- flatMap, flatMapToDouble, flatMapToInt, flatMapToLong : 스트림 요소 변환
- 최종 연산 : 연산 결과가 스트림이 아닌 연산. (스트림의 요소를 소모하므로 한 번만 가능)
- forEach, forEachOrdered : 각 요소에 작업 수행
- count : 요소 개수 반환
- max, min : 최대, 최솟값 반환
- findAny, findFirst : 아무거나, 첫 번째 요소 반환
- allMatch, anyMatch, noneMatch : 주어진 조건을 모두, 하나라도, 불만족 확인
- toArray : 스트림 요소를 배열로 반환
- reduce : 요소를 리듀싱(누산)
- collect : 컬렉션에 담아 반환
stream
.distinct() // 중간 연산
.limit(7) // 중간 연산
.sorted() // 중간 연산
.forEach(System.out::println) // 최종 연산
이제 연산별 메서드가 무엇이 있는지 알았으니 필요할 때마다 찾아보자.
# Stream 생성
Stream 선언은 Stream<T>이지만 오토박싱, 언박싱 비효율을 줄이기 위해 IntStream 같은 기본형 Stream도 제공한다.
근데 킹 받는 것은 Boolean형 Stream은 제공하지 않는다. (Stream<Boolean>은 가능함)
사실 메서드 시그니처만 보고 감으로 Stream API 쓰다가 BooleanStream이 없어서 공부하게 되었다,,
BooleanStream 제공 안 하는 이유
import java.util.stream.*;
Stream<T> stream = Stream.of(T...);
Stream<T> stream = Stream.of(T[]);
Stream<T> stream = Arrays.stream(T[]);
Stream<T> stream = Arrays.stream(T[], 포함할 시작 인덱스, 제외할 시작 인덱스);
// 기본형 스트림
IntStream intStream = IntStream.of(T...);
IntStream intStream = IntStream.of(T...);
IntStream intStream = Arrays.stream(int[]);
IntStream intStream = Arrays.stream(int[], 포함할 시작 인덱스, 제외할 시작 인덱스);
LongStream LongStream = ...; (IntStream에서 LongStream 사용 외 같음)
DoubleStream doubleStream = ...; (IntStream에서 DoubleStream 사용 외 같음)
# Stream 변환
강타입 언어 특성상 형변환이 제일 빡세다.
Stream 역시 마찬가지므로 자주 변환할 것 같은 리스트를 정리해 둔다.
1. Stream → 기본형 Stream
map() 파생 메서드를 사용한다.
Stream<Integer> intStream = Stream.of(1, 2, 3);
IntStream primitiveIntStream = intStream.mapToInt(el -> el); // 람다식
IntStream primitiveIntStream = intStream.mapToInt(Integer::valueOf); // 메서드 참조로도 가능
Stream<Long> longStream = Stream.of(1L, 2L, 3L);
LongStream primitiveLongStream = longStream.mapToLong(el -> el);
LongStream primitiveLongStream = longStream.mapToLong(Long::valueOf);
Stream<Double> doubleStream = Stream.of(1.0, 2.0, 3.0);
DoubleStream primitiveDoubleStream = doubleStream.mapToDouble(el -> el);
DoubleStream primitiveDoubleStream = doubleStream.mapToDouble(Double::valueOf);
2. 기본형 Stream → Stream
boxed() 메서드를 사용한다.
IntStream primitiveIntStream = IntStream.of(1, 2, 3);
Stream<Integer> intStream = primitiveIntStream.boxed();
LongStream primitiveLongStream = LongStream.of(1L, 2L, 3L);
Stream<Long> longStream = primitiveLongStream.boxed();
DoubleStream primitiveDoubleStream = DoubleStream.of(1.0, 2.0, 3.0);
Stream<Double> doubleStream = primitiveDoubleStream.boxed();
3. Stream → 컬렉션
collect() 메서드를 사용한다. (기본형 Stream은 boxed()로 박싱 해야 함)
// List
Stream<Integer> intStream = Stream.of(1, 2, 3);
List<Integer> intList = intStream.collect(Collectors.toList());
IntStream primitiveIntStream = IntStream.of(1, 2, 3);
List<Integer> intList = primitiveIntStream.boxed().collect(Collectors.toList());
// Set
Stream<Integer> intSetStream = Stream.of(1, 2, 3);
Set<Integer> intSet = intSetStream.collect(Collectors.toSet());
IntStream primitiveIntStream = IntStream.of(1, 2, 3);
Set<Integer> intSet = primitiveIntStream.boxed().collect(Collectors.toSet());
// Map
Stream<Object[]> mapStream = Stream.of(new Object[][]{{"apple", 13}, {"banana", 10}});
Map<String, Integer> map = mapStream.collect(
Collectors.toMap(el -> (String) el[0], el -> (Integer) el[1]));
4. 컬렉션 → Stream
stream() 메서드를 사용한다.
Stream<Integer> intStreamFromArrayList = new ArrayList<Integer>().stream();
Stream<Integer> intStreamFromSet = new HashSet<Integer>().stream();
5. Stream → 배열
toArray() 메서드를 사용한다. (기본형 Stream이 아닐 경우 1번처럼 변환해주어야 함)
Stream<Integer> intStream = Stream.of(1, 2, 3);
int[] intArray = intStream.mapToInt(el -> el).toArray(); // 람다식
int[] intArray = intStream.mapToInt(Integer::valueOf).toArray(); // 메서드 참조로도 가능
IntStream primitiveIntStream = IntStream.of(1, 2, 3, 4, 5);
int[] intArray = primitiveIntStream.toArray();
'Language & Runtime > Java' 카테고리의 다른 글
[Java] BufferedReader와 BufferedWriter (0) | 2022.12.13 |
---|