문제점
합계, 평균 등 데이터를 집계할 때, for 또는 Iterator를 활용해서 코드를 작성해왔다. 이러한 코드는 가독성이 떨어지고 번거롭다...(매우...같은 코드 짜다보면 너무 답답..)
또한, 데이터 소스마다 다른 방식으로 다뤄야 한다.
이러한 문제점!을 해결하기 위해 등장한 것이 Stream이다.
→ 데이터 소스마다 같은 방식으로 다룰 수 있도록 데이터를 추상화하고, 자주 사용되는 메서드를 정의해 놓았다.
sum, average, min, max 등등
특징 : SQL의 쿼리 같은 느낌
- 스트림은 데이터 소스를 변경하지 않는다.
abc.stream().~~~해도 abc라는 데이터 소스는 아무런 영향을 받지 않는다. stream은 읽기만 하는 것이다.
- 스트림은 일회용이다.
stream을 생성하고 최종 연산(ex foreach)을 실행하면 해당 stream이 닫히기 때문에 다시 사용할 수 없다.
* 중간 연산 : 연산 결과가 스트림인 연산을 뜻하며, 연속해서 중간 연산이 가능하다.
* 최종 연산 : 연산 결과가 스트림이 아닌(map..)연산을 뜻하며, 스트림을 소모한다.
- 지연된 연산 : 최종 연산이 실행되기 전까지 중간 연산은 실행되지 않는다.
- 스트림은 작업을 내부 반복으로 처리한다.
스트림 만들기 : 파이썬에서만 되는 줄 알았던 기능들이 java에도 있었다!!
- 컬렉션 : Collection.stream() 으로 스트림 생성
- 배열 : Stream.of(T[ ]), Arrays.stream(T[ ]) → static method이다.
- 특정 범위의 정수 : IntStream.range(int begin, int end)
- 임의의 수 : Random.ints(), Random.longs(), Random.doubles() ← 무한 스트림 생성. limit으로 잘라줘야한다.
또는 Random.ints(streamSize), Random.ints(streamSize, begin, end)[end는 포함 안됨]
- iterate, generate : Stream.iterate(0,n,->n+2), Stream.generate(()->1) ← 무한 스트림 생성. limit으로 잘라줘야한다.
- Files.list(Path dir) : 지정된 디렉토리에 있는 파일의 목록을 소스로 하는 스트림 생성해서 반환.
* Path : 하나의 파일 또는 경로., java.nio.file.Files
- 빈 스트림 : Stream.empty() : 수행결과가 하나도 없을때 null보다 빈 스트림을 반환하는 것이 낫다.
- 스트림의 연결 : concat → static method이다.
String[] str1 = { "123", "456", "789" };
String[] str2 = { "abs", "asdf", "tte" };
Stream<String> s1 = Stream.of(str1);
Stream<String> s2 = Stream.of(str2);
Stream<String> s3 = Stream.concat(s1, s2);
s3.forEach(System.out::println);
>> 결과
123
456
789
abs
asdf
tte
스트림의 중간연산
스트림 자르기 : skip(), limit()
- skip(long n) : n개 만큼 건너 뛴다.
- limit(long maxSize) :요소의 개수를 maxSize로 제한한다.
- distinct : 중복 요소 제거
- filter(Predicate<? super T> predicate) : predicate에 맞지 않는 요소 걸러내기.
- sorted : 정렬. sorted(Comparator<? super T> comparator)
- map : 요소에서 원하는 필드만 뽑거나 특정 형태로 변화할 때 사용. 굉장히 유용.
Stream<R> map (Function<? super T, ? extends R> mapper)
- peek() : 조회, foreach와 달리 스트림을 소모하지 않아서 연산 사이에 여러번 끼워 넣어도 문제되지 않는다.
- mapToInt(), mapToDouble, mapToLong() : 오토 박싱, 언박싱으로 인한 비효율을 해소한다.
→ summaryStatistics() : 평균과 합계 등 모든 통계값을 구하는 메서드를 따로 제공한다.
- flatMap() : Stream<T[]>를 Stream<T>로 변환
→ 펼쳐준다고 생각하면 된다. [[a,b,c],[d,e,f]] -> [a,b,c,d,e,f] 요렇게!
스트림 최종연산
스트림에서 가장 중요한 것이라 생각한다. 앞서 언급했지만, 최종연산을 하게 되면 스트림의 요소들이 소모된다! 최종연산을 진행한 스트림은 재활용이 불가능하다.
먼저, 간단한 forEach부터..
- forEach : void 반환타입으로 값 출력할 때 많이 사용한다.
void forEach(Consumer<? super T> action)
- 조건 검사 : allMatch(), anyMatch, noneMatch(), findFirst(), findAny()
boolean allMatch (Predicate<? super T> predicate) // 모두 조건에 해당하면 true
boolean anyMatch (Predicate<? super T> predicate) // 하나라도 조건에 해당하면 true
boolean noneMatch (Predicate<? super T> predicate) // 전부 조건에 해당하지 않으면 true. 하나라도 조건에 해당하면 false
findFirst : 조건에 일치하는 첫번째 요소 반환. → Optional<T> 리턴, 요소가 없을 때는 빈 Optional객체 리턴
findAny : 조건에 일치하는 아무거나 반환. 병렬스트림일 때 주로 사용. → Optional 리턴, 요소가 없을 때는 빈 Optional객체 리턴
- 통계 : count, max, min, sum, average ( sum, average는 기본형 스트림에만 존재한다)
long count()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
→ 대부분의 경우 기본형 스트림으로 변환 또는 reduce, collect를 사용해 통계 정보를 얻는다.
- 리듀싱 - reduce()
스트림 요소를 줄여나가면서 연산 수행 및 최종결과 반환. 그래서 매개변수 타입이 BinaryOperator<T>!이다. 처음 두 요소로 연산한 결과를 가지고 다음 요소와 연산을 진행한다.
Optional<T> reduce (BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator) // 초기값과 첫번째 요소로 계산 시작
U reduce(U identity, BiFunction<T, T, U> accumulator, BinaryOperator<U> combiner) // 병렬처리할 때 다룸.
- collect() : 생각보다 메서드가 많다.
collect() 스트림의 최종연산, 매개변수로 컬렉터를 필요로 한다.
Collector() 인터페이스, 컬렉터는 이 인터페이스를 구현해야한다.
Collectors 클래스. static메서드로 미리 작성된 컬렉터를 제공한다.
1) toList, toSet, toMap, toCollection, toArray : 스트림 요소 컬렉션으로 수집
스트림 요소를 컬렉션에 수집하기 위해서는 위 메서드를 사용한다.
List<String> names =ssStream.map(Student::getName).collect(Collectors.toList());
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = personStream.collect(Collectors.toMap(p->p.getRegId(), p->p));
//map의 경우 key, value를 어떤것을 사용할 지 지정해주어야 한다.)
특정 컬렉션 지정을 위해서는 toCollection에 해당 컬렉션 생성자 참조를 매개변수로 넣어준다.
*항등함수 : Function.identity() 사용가능.
Student[] stuNames = studentStream.toArray(Student[]::new); // OK 생성자 참조를 매개변수로 지정해야한다.
Student[] stuNames = studentStream.toArray(); // 에러남.
Object[] stuNames = studentStream.toArray(); // ok. 매개변수 없을때는 Object[]를 리턴.
2) joining : 문자열 연결
joing(문자열 사이, 문자열 시작, 문자열 끝)으로 지정할 수 있다.
String[] att = { "a", "b", "c", "d" };
System.out.println(Stream.of(att).collect(Collectors.joining())); // abcd
System.out.println(Stream.of(att).collect(Collectors.joining(","))); // a,b,c,d
System.out.println(Stream.of(att).collect(Collectors.joining(",","[","]"))); // [a,b,c,d]
3) groupingBy(), partitioningBy() : 그룹화와 분할
groupingBy() : 특정 기준으로 그룹화
partitioningBy() : 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로 분할
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
정리
Stream은 데이터 처리에 필수!! 계속 써서 익숙해지도록 하는 게 중요하다!
(수십줄짜리 코드를 단 한줄로, 해결가능!)
collect 메서드에 대해서는 추후 포스팅 하도록 해야겠다. good night!
'Java' 카테고리의 다른 글
[Java] 소스코드 탐험- Iterator (0) | 2023.07.23 |
---|---|
[Java] List정렬(sort)/ Collections.sort, List.sort (0) | 2023.02.11 |
[Java] Optional<T>, Optional 타입 : 래퍼 클래스, 코드의 복잡성을 낮추다. (0) | 2022.09.04 |
[Java] Wrapper 클래스 : 기본 타입의 데이터를 객체로 취급하다.( 오토박싱, 오토언박싱) (0) | 2022.09.03 |
[Java] 람다식 - Lambda Expression (0) | 2022.08.29 |
댓글