
데이터를 다룰때 for문이나 iterator을 이용해 코드를 작성한다. 그러나 이러한 방식은 코드가 너무 길고 재사용하기 어렵다.
이를 해결하기 위해, 데이터 소스를 추상화하고, 자주 사용되는 데이터를 다루는 방법에 대한 메서드를 미리 정의해둔 것이 Stream이다. Java 8부터 사용 가능하다.
이름과 데이터 소스를 추상화 했기에 데이터를 조작하는 흐름에 집중해서 프로그래밍 할 수 있다.
1. 데이터 소스를 변경하지 않는다.
스트림은 기존의 데이터를 읽기만 하며 데이터 소스를 변경하지 않는다.
2. 일회용이다.
Iterator는 컬렉션 요소를 모두 읽고 나면 다시 사용할 수 없다. 스트림도 한번 사용하고나면 다시 스트림을 생성해서 사용한다.
3. 작업을 내부 반복으로 처리한다.
반복문을 메서드 내부에 숨겨서 처리한다는 것이다. 덕분에 스트림은 간결한 구조를 가진다.
4. Lazy 한 연산
스트림 연산에서 특이한 점은 lazy하다는 것이다. 최종 연산이 수행되는 시점까지 중간 연산은 수행되지 않는다.
5. 병렬 처리 지원
// Collections -> Steam<T>
Stream<T> Collections.steam();
// Arrays -> Steam<T> (둘 중 하나)
Stream<T> Stream.of(T[] arr);
Stream<T> Arrays.stream(T[] arr);
IntStream, LongStream 등은 기본형 배열을 다룰 수 있는 Stream인데 기본 Stream과 달리 박싱 언박싱이 자동으로 안이루어져 성능상 좋다. 기본형 배열을 최종결과로 반환하려면 반드시 해당 Stream들로 변환이 필요하다.
String[] strArr = { "aaa", "ddd", "ccc" } ;
Stream<String> strStream = Arrays.stream(strArr);
strStream.distinct().limit(5).sorted().forEach(System.out::println);
// 중간연산 3개 + 최종연산 1개
스트림의 연산은 일종의 쿼리 문과 비슷하며, 중간연산과 최종연산으로 분류된다.
중간연산 : 연산 결과가 스트림인 연산. 즉 연속해서 연산 가능
최종연산 : 연산 결과가 스트림이 아닌 연산. 즉 마지막에 단 한번만 가능
물론 최종연산은 데이터 소스를 소모하는 것은 아니기에 같은 소스에서 다시 스트림을 만들면 된다.
String[] strArr = { "aaa", "ddd", "ccc" } ;
Stream<String> strStream = Arrays.stream(strArr);
strStream.distinct();
strStream.distinct(); // 중간연산이라 문제 없음
strStream.forEach(System.out::println);
strStream.forEach(System.out::println); // 에러, 최종연산이라 위에서 이미 스트림이 닫힘
연산 결과가 스트림이라 연달아 호출가능하다.
Filter
조건에 안 맞는 요소 제외
Stream<T> filter(Predicate<T> predicate);
stream.filter(list -> list.contails("a"));
Boxed
stream.boxed() // 박싱
.mapToInt(Integer::intValue) // 언박싱 Map이기에 밑에서도 설명한다.
Map
스트림의 요소를 변환한다.
Stream<R> map(Function<T, R> mapper);
stream.map(Integers::parseInt);
// 문자열을 정수로 변환
또한 스트림을 변환하는 것도 가능하다.
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
// Stream<String> -> IntStream
.mapToInt(Integer::parseInt)
// Stream<Integer> -> IntStream
.mapToInt(Integer::intValue) // 언박싱 메서드 사용
Sorted
스트림의 요소를 정렬한다.
Stream<T> sorted();
Stream<T> sorted(Comparator<T> action);
stream.sorted(); // 오름차순 정렬
stream.sorted(Comparator.reverseOrder()); // 내림차순 정렬
Distinct
중복 제거
Stream<T> distinct();
Limit
스트림의 일부 잘라내기
Stream<T> limit(long maxSize);
stream.limit(5);
Skip
스트림의 일부 건너뛰기
Stream<T> skip(long n);
stream.skip(3);
Peek
요소에 작업수행 (중간 점검등에 사용)
Stream<T> peek(Consumer<T> action);
stream.peek(System.out::println);
연산 결과가 스트림이 아니라 마지막에 한번만 호출 가능하다.
ForEach
각 요소에 지정된 작업 수행
void forEach(Consumer<? super T> action);
stream
.map(Person::getName)
.forEach(System.out::println);
Match
각 요소를 만족하는지 확인
boolean allMatch(Predicate<T> p); // 모두 만족하는지
boolean anyMatch(Predicate<T> p); // 하나라도 만족하는지
boolean noneMatch(Predicate<T> p); // 모두 만족하지 않는지
boolean result1 = stream.allMatch(members->members.contains("w"));
boolean result2 = stream.anyMatch(members->members.contains("w"));
boolean result3 = stream.noneMatch(members->members.contains("w"));
toArray
스트림 모든 요소를 배열로 반환
Object[] toArray();
A[] toArray(IntFunction<A[]> generator);
Object[] result = stream.toArray();
String[] result = stream.toArray(String[]::new); // 참조타입 배열만 이런식으로 가능, 원시타입 배열은 반드시 다른 Stream으로 변환 후 toArray();
collect
Stream 모든 원소를 하나의 객체로 통합하는 메서드 (StringBuilder와 궁합 좋음)
R collect (Supplier<R> supplier, BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner);
// supplier : 반환할 객체
// accumulator : 반환할 객체에 stream원소 누적 방식
// combiner : 객체가 여러개 있다면 어떻게 합치는지 정의(멀티스레드 환경에서)
// char를 stream으로 다루는 즉 IntStream을 예시로 {'a', 'b'} -> "ab"
char[] arr = {'a', 'b'};
IntStream stream = Arrays.stream(arr);
String result = stream.collect(StringBuilder::new,
StringBuilder::appedCodePoint, // int를 char로 넣기
StringBuilder::append)
.toString();
연산
기본형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합과 같은 연산을 수행할 수 있다.
stream
.count() //스트림 요소 개수 반환
.sum() //스트림 요소의 합 반환
.min() //스트림의 최소값 반환
.max() //스트림의 최대값 반환
.average() //스트림의 평균값 반환
Reduce
스트림의 요소를 하나씩 줄여가면서 계산
T reduce(T identity, BinaryOperator<T> accumulator accumulator);
IntStream stream = IntStream.range(1,5);
stream.reduce(10, (total,num)->total+num);
//reduce(초기값, (누적 변수,요소)->수행문)
// 10 + 1+2+3+4+5 = 25
Collect
스트림의 요소를 수집한다.
R collect(Collector<T, A, R> collector);
//예시 리스트
List<Person> members = Arrays.asList(new Person("lee",26),
new Person("kim", 23),
new Person("park", 23));
// toList() - 리스트로 반환
members.stream()
.map(Person::getLastName)
.collect(Collectors.toList());
// [lee, kim, park]
// joining() - 작업 결과를 하나의 스트링으로 이어 붙이기
members.stream()
.map(Person::getLastName)
.collect(Collectors.joining(delimiter = "+" , prefix = "<", suffix = ">");
// <lee+kim+park>
//groupingBy() - 그룹지어서 Map으로 반환
members.stream()
.collect(Collectors.groupingBy(Person::getAge));
// {26 = [Person{lastName="lee",age=26}],
// 23 = [Person{lastName="kim",age=23},Person{lastName="park",age=23}]}
//collectingAndThen() - collecting 이후 추가 작업 수행
members.stream()
.collect(Collectors.collectingAndThen (Collectors.toSet(),
Collections::unmodifiableSet));
//Set으로 collect한 후 수정불가한 set으로 변환하는 작업 실행
Find
스트림의 요소 하나를 반환
Optional<T> findAny(); // 아무거나 하나
Optional<T> findFrist(); // 첫번째 요소 하나
// String -> IntStream
// "abc" -> ['a', 'b', 'c']
// 문자로 구성된 Stream이지만 char도 정수라 IntStream으로 다룸 CharStream은 없음
IntStream String.chars();
// IntStream -> Stream<String>
// 원시타입 스트림에서 참조타입 스트림으로 변환시 mapToObj
.mapToObj(String::valueOf)
// 단순 박싱의 경우 밑으로 대체 가능
.boxed()
// Stream<String> -> IntStream
// {"123", "234"} -> {123, 234}
.mapToInt(Integer::parseInt)
// Stream<Integer> -> IntStream
// {1, 2, 3}(Integer) -> {1, 2, 3}(int)
.mapToInt(Integer::intValue) // 언박싱
자바의 정석 - 남궁성
https://velog.io/@yun8565/Java-%EC%8A%A4%ED%8A%B8%EB%A6%BCStream-%EC%A0%95%EB%A6%AC