[JAVA] 람다와 스트림 ( Lambda & Stream ) ⑪

DongGyu Jung·2022년 5월 5일
0

자바(JAVA)

목록 보기
59/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 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 과 같은 컬렉션이 아닌
ArrayListLinkedList 등과 같은
특정 컬렉션을 지정할 때에는

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) 작업에 유리한 메서드이다.

리듀싱(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 ) ); 


⑤ 그룹화 & 분할

※ 『그룹화와 분할』 과 관련된 내용은 다음 게시물 에서 이어 보자!

0개의 댓글