
🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 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 ) );
※ 『그룹화와 분할』 과 관련된 내용은 다음 게시물 에서 이어 보자!