🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕
*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.
collect()
& Collectors
스트림의 최종 연산 중에서
가장 복잡하면서도 유용하게 활용될 수 있는 최종 연산이 collect()
라고 한다.
우선 collect()
는
" Collector
인터페이스 구현 클래스 객체 "를
매개변수로 받는 메서드로
앞서 배운 reduce()
를 통해 수행했던 리듀싱(reducing)과 유사한
스트림 요소 수집 연산이다.
reducing과의 차이점이라고 한다면
수행 범위 에 있다.
reduce()
는 스트림 전체에 대해서 Reducing할 때
collect()
는 요소 그룹별로 Reducing 할 때
사용된다.
이 collect()
는
sort()
메서드에게는 Comparator
가 필요한 것 처럼
정의된 Collector
가 필요하다.
Object collect( Collector collector )
Object collect( Supplier supplier, BiConsumer accumulator, BiConsumer combiner )
// ↑ 이건 잘 안쓰임
여기서 Collector
인터페이스는
collect()
가 스트림의 요소를 수집할 때,
" 수집에 필요한 메서드 "를 정의해 놓은 인터페이스이다.
/* T(요소) --> A에 누적(reducing) --> 결과 R로 변환&반환 */
public interface Collector<T, A, R> {
// 누적할 곳 _ex. StringBuilder::new
Supplier<A> supplier();
// 누적 방법 _ex. (sb, s) -> sb.append(s)
BiConsumer<A, T> accumulator();
// 결합 방법(병렬) _ex. (sb1, sb2) -> sb1.append(sb2)
BinaryOperator<A> combiner();
// 최종 변환 (R타입으로) _ex. sb -> sb.toString()
Function<A, R> finisher();
// 컬렉처 특성 Set 반환
Set<Characteristics> characteristics() ;
....
...
}
하지만 이 Collector
인터페이스를 모두 직접 구현하기에는
조금 무리가 있다..
다행히도 직접 구현하지 않더라도
미리 작성된 다양한 종류의 " 컬렉터 반환 static 메서드 " 가
《 Collectors
클래스 》에 있다.
🔔
Collectors
클래스의 Collector 반환 static 메서드
- 변환 :
mapping()
,toList()
,toSet()
,toMap()
,toCollection()
, ...
- 통계 :
counting()
,summingInt
,averagingInt()
,maxBy
,minBy()
,summarizingInt()
, ...
- 문자열 결합 :
joining()
- 리듀싱(Reducing) :
reducing()
- 그룹화 & 분할 :
groupingBy()
,partitioningBy()
,collectingAndThen()
위에 보이는
수행 기능 별 Collectors
클래스 메서드에 대해
더 자세히 알아보자.
Collectors
클래스 메서드※
import static java.util.Stream.Collectors.*
를 통해Collectors.
를 코드에서 생략하고 간결하게 쓰자!!
toList()
, toSet()
, toMap()
, toCollection()
스트림의 모든 요소를 컬렉션에 수집하려면
위 메서드들을 사용하면 되는데
List나 Set 과 같은 컬렉션이 아닌
ArrayList
나 LinkedList
등과 같은
특정 컬렉션을 지정할 때에는
toCollection()
메서드에
" 원하는 컬렉션의 생성자 참조 "를 매개변수로 넣어주면 된다.
List<String> names = childrenStream.map(Child::getName).collect( Collectors.toList() );
// String 요소 List 객체 스트림 -> ArrayList 컬렉션에 저장
ArrayList<String> list = names.stream().collect( Collectors.toCollection(ArrayList::new) ) ;
또한 toMap()
의 경우엔
(Key -Value) 쌍 으로 저장해야하므로
연산 대상인 객체의
" 어떤 필드를 키로 사용할지 "와
" 어떤 필드를 값으로 사용할지 "를 지정해줘야 한다.
// 주민번호 -> 키 & 객체 -> 값
Map<String, Person> map = personStream.collect( Collectors.toMap(p -> p.getRegId(), p -> p) );
toArray()
스트림의 저장 요소들을 배열로 변환할 때는 toArray()
메서드를 사용하면 되는데
주의해야 할 점은
매개변수로 해당 타입의 생성자 참조를 넣어줘야하고
만약 넣지 않는다면
Object[]
타입의 배열이 반환되므로
배열을 담을 참조변수 타입도 Object[]
로 일치시켜줘야한다.
Child[] childrenNames = childrenStream.toArray(Child[]::new) ;
Child[] childrenNames = childrenStream.toArray() ; // 에러 : Object[] 배열 반환 -> 불일치
Child[] childrenNames = (Child[])childrenStream.toArray() ;
Object[] childreanNames = childrenStream.toArray() ;
그룹별 통계에 있어 유리한 메서드들이다.
이전에 알아봤었던
최종 연산들을 collect()
로 모두 똑같이 얻을 수도 있다.
( 당연히, 해당 단일 기능만으로 쓰기에는 collect()
를 쓰는 방법은 번거롭기 때문에
이전에 알아봤던 최종 연산들을 사용하는게 바람직하다.)
나중에 알아볼 그룹화 메서드인 groupingBy()
라는 메서드와
주로 함께 사용되기 때문에 통계 메서드의 사용법을 짚고 넘어갈 필요가 있다.
counting()
, summingInt
, averagingInt()
, maxBy
, minBy()
, summarizingInt()
, ...summingInt()
와 summarizingInt()
혼동 주의 )import static java.util.Stream.Collectors.* ;
/* static 메서드이기 때문에 import하고 짧게 쓰자. */
long count = stuStream.count();
long count = stuStream.collect( counting() ); // Collectors
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore = stuStream.collect( summingInt(Student::getTotalScore) ); // Collectors
Optional<Student> topStudent = stuStream.max( Comparator.compareInt(Student::getTotalScore) );
Optional<Student> topStudent = stuStream.collect( maxBy( Comparator.compareInt(Student::getTotalScore) ) );
IntSummaryStatistics stat = stuStream.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream.collect( summarizingInt( Student::getTotalScore ) );
Python 라이브러리 함수 중에서 .join()
와 유사한 메서드이다.
바로 joining()
이다.
" 구분자 " & " 접두사 " & " 접미사 " 모두 지정 가능하다.
단,
스트림의 요소가
String
이나 StringBuffer
처럼
CharSequence
의 자손인 경우에만 결합이 가능하기 때문에
요소가 문자열이 아닌 경우에는
map()
을 통해 먼저 문자열로 변환해야한다.
( map()
을 안하고 실행하면 toString()
호출 결과 를 결합하게 된다. )
import static java.util.Stream.Collectors.* ;
/* 이름 : Kim, Jung, Choi, Park 이 있다고 가정하자. */
// 출력 >> KimJungChoiPark
String stuNames = stuStream.map(Student::getName).collect( joining() ) ;
// 출력 >> Kim,Jung,Choi,Park
String stuNames = stuStream.map(Student::getName).collect( joining(",") ) ;
// 출력 >> [Kim, Jung, Choi, Park]
String stuNames = stuStream.map(Student::getName).collect( joining(",", "[", "]") ) ;
그룹별 리듀싱(Reducing) 작업에 유리한 메서드이다.
리듀싱(Reducing) 작업도 collect()
로 가능한데
Collector reducing( BinaryOperator<T> op )
// ★★★★★★★★
Collector reducing( T identity, BinaryOperator<T> op )
// 변환 작업까지 필요한 경우
Collector reducing( U identity, Function<T, U> mapper, BinaryOperator<U> op )
기존의 Stream 최종연산 reduce()
와의 사용 방법 차이를 알아보자.
import static java.util.Stream.Collectors.* ;
// 또 다시 로또 번호 뽑기
IntStream intStream = new Random().ints(1, 46).distinct().limit(6) ;
OptionalInt max = intStream.reduce(Integer::max) ; // 전체 대상 reducing 에 유리
Optinal<Integer> max = intStream.boxed().collect( reducing( Integer::max ) );
보이는 것 처럼
기본형 스트림인 IntStream
에는 매개변수 3개짜리 collect()
만 정의되어 있기 때문에
Stream<Integer>
로 boxed()
를 통해 변환해야만
" 매개변수 1개짜리 collect()
"를 쓸 수 있다는 것을
알고 있어야한다.
long sum = intStream.reduce( 0, (a,b) -> a + b) ;
long sum = intStream.boxed().collect( reducing( 0, (a,b) -> a + b ) );
int grandTotal = stuStream.map(Student::getTotalScore).reduce( 0, Integer::sum) ;
// 초기값 설정, 변환작업, 수행작업 reducing
int grandTotal = stuStream.collect( reducing( 0, Student::getTotalScore, Integer::sum ) );
※ 『그룹화와 분할』 과 관련된 내용은 다음 게시물 에서 이어 보자!