🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕
*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.
Collectors 클래스의 기능별 메서드들에 대한 게시글을
이어받아
그룹화와 분할 기능의 메서드를 알아보도록 하자.
그룹화는 스트림의 요소를 " 특정 기준 "으로 그룹화하는 것이고
분할은 스트림의 요소를 〔조건 일치 그룹〕 / 〔조건 불일치 그룹〕
두 가지 그룹으로의 분할을 의미한다.
위 설명만으로는 사실 두 가지 작업은 결과물이 두 가지 그룹으로 나뉘는 모습으로
비슷하다고 느낄 수 있다.
반환 결과 또한 둘 다Map
에 담겨 반환된다.
하지만 차이는
분할의 경우, 일치 & 불일치 로 2분할만 가능하지만
그룹화의 경우, 특정 기준에 의해 N 분할이 가능하다.
또한,
매개변수에서
분류기준을 Function
으로 하느냐 / Predicate
로 하느냐에 있다.
만약 스트림을 두 개의 그룹으로 나눠야 한다면
partitioningBy()
메서드를 통해 분할하는 것이 더 빠르다.
Collector partitioningBy( Predicate predicate )
Collector partitioningBy( Predicate predicate, Collector downstream )
앞서 반환결과가 Map에 담겨 반환된다고 설명했는데
2분할에서의 Key는
당연히 기준에 대한 true / false 두 개의 값이 되고
Value는
각 결과가 Key에 해당하는 값들에 대한 처리값이 될 것이다.
먼저 간단하게 대표적인 2분할인 성별을 분할해보자.
// 기본 분할 -> 기준 적용 결과가 Key값에 해당하는 값들의 List
Map<Boolean, List<Student>> stuBySex = stuStream.collect( partitioningBy(Student::isMale) );
List<Student> maleStudent = stuBySex.get(true) ; //남학생 리스트
List<Student> femaleStudent = stuBySex.get(false) ; //여학생 리스트
위에서 사용했던 각 성별 학생들의 리스트로 나눈 기본 분할 예제를 활용해보자.
우선 counting()
을 추가해 각 그룹의 학생 수를 구하는 예제이다.
( summingLong()
을 대신 사용하면 각 그룹의 총점을 구할 수 있다. )
Map<Boolean, Long> stuNumBySex = stuStream.collect( partitioningBy( Student::isMale, counting() ) );
System.out.println("남학생 인원 : " + stuNumBySex.get(true)) ; //남학생 인원
System.out.println("여학생 인원 : " + stuNumBySex.get(false)) ; //여학생 인원
이번엔 이건 게시물에서 살펴봤었던
maxBy()
메서드를 통해
"각 성별 그룹에서의 1등"을 뽑아보자.
Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect(
partitioningBy( Student::isMale, maxBy( comparingInt(Student::getScore) ) ) );
System.out.println("남학생 1등 : " + topScoreBySex.get(true)) ; // 추출된 "Optional"객체의 toString() 결과
System.out.println("여학생 1등 : " + topScoreBySex.get(false)) ; //추출된 "Optional"객체의 toString() 결과
여기에서
maxBy()
메서드의 반환값은Optional<T>
타입이기 때문에
만약 해당 결과 값 객체의 타입으로 반환받고 싶다면
collectingAndThen()
을 활용해
해당 함수의 매개변수로maxBy()
실행문과 더불어Optional::get
을 두 번째 매개변수로 입력하면 된다.Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect( partitioningBy( Student::isMale, collectingAndThen( maxBy(comparingInt(Student::getScore)), Optional::get ) ) ); System.out.println("남학생 1등 : " + topScoreBySex.get(true)) ; // 추출된 객체의 toString() 결과 System.out.println("여학생 1등 : " + topScoreBySex.get(false)) ; //추출된 객체의 toString() 결과
이번에 분할을 두 단계 깊이에 걸쳐 수행하는 것이다.
예를 들면
" 각 성별 그룹에서 성적 기준 합격/불합격으로 분할 "과 같은 작업을 해야한다면
간단하게 할 수 있다.
바로
매개변수 중 Collector downstream
의 위치에 다시 partitioningBy()
를 입력하는 것이다.
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream.collect(
partitioningBy( Student::isMale,
partitioningBy( s -> s.getScore() < 100 ) ) );
List<Student> failedMaleStudent = failedStuBySex.get(true).get(true) ; //"불합격" 남학생 리스트
List<Student> failedFemaleStudent = failedStuBySex.get(false).get(true) ; //"불합격" 여학생 리스트
Collector groupingBy( Function classifier )
Collector groupingBy( Function classifier, Collector downstream )
Collector groupingBy( Function classifier, Supplier mapFactory, Collector downstream )
처음에 분할과의 차이점으로
N 분할이 가능하다고 했듯이
기준을 어떻게 주느냐에 따라
각 설정에 기준에 따른 값들끼리의 Grouping이 처리된다.
그리고
groupingBy()
는 분할과는 다르게
기본적으로 Value값은 "리스트List<T>
" 에 담는다.
(물론 다른 컬렉션으로 지정할 수 있다._이전 게시물 참고)
먼저
만약 특정 필드의 값을 기준으로 Grouping을 할때
해당 필드의 값이
특정 도메인 값으로 나눠져있다면
getter메서드를 통해 간단하게 값에 따라 나눌 수 있다.
// 기본적으로 Value가 List로 반환되기 때문에 toList() 생략
Map<Integer, List<Student>> stuByBan = stuStream
.collect( groupingBy(Student::getBan) ) ;
/*
참고!!
다른 컬렉션으로 바꿔보기
*/
Map<Integer, HashSet<Student>> stuByBan = stuStream
.collect( groupingBy(Student::getBan, toCollection(HashSet::new) ) ;
조금 더 응용해서
값을 넘어 if ~ else if ~ else
을 활용해서
여러 조건식에 따른 Grouping을 해보자.
이번 예제는 " 성적 등급에 따른 분류 및
각 등급에 해당하는 학생 수 "를 구하는 문제이다.
Map<Student.Level, Long> stuByLevel = stuStream
.collect( groupingBy( s -> {
if(s.getScore() >= 200)
return Student.Level.HIGH;
else if(s.getScore() >= 100)
return Student.Level.MID;
else
return Student.Level.LOW;
}, counting() ) ) ;
List<Student> failedMaleStudent = failedStuBySex.get(true).get(true) ; //"불합격" 남학생 리스트
List<Student> failedFemaleStudent = failedStuBySex.get(false).get(true) ; //"불합격" 여학생 리스트
Grouping도 분할과 마찬가지로
다중 Grouping이 가능하다.
방법은 간단하다.
// 학년별 그룹화 --> 반별 그룹화
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
.collect( groupingBy( Student::getHak , groupingBy(Student::getBan) ) );
여기에서 더 심화적으로
" 각 학년의 각 반별 학생들 " +
" 성적 등급별 그룹화 & 각 등급에 해당하는 학생 수 "
두 기준을 합쳐보자.
Map<Integer, Map<Integer, Set<Student.Level>>> stuByHakAndBan =
stuStream.collect(
groupingBy( Student::getHak ,
groupingBy(Student::getBan,
mapping(s -> {
if(s.getScore() >= 200)
return Student.Level.HIGH;
else if(s.getScore() >= 100)
return Student.Level.MID;
else
return Student.Level.LOW;
}, toSet() )
)
)
);
알아본 김에
각 반 1등( maxBy()
)과 해당 값 객체 타입으로 받아오는 것( collectingAndThen()
& Optional::get
)도 해보자.
// Optional::get을 통해 Student객체로 값 추출
Map<Integer, Map<Integer, Student>> topStuByHakAndBan =
stuStream.collect(
groupingBy( Student::getHak ,
groupingBy(Student::getBan,
collectingAndThen(
maxBy( comparingInt(Student::getScore) ),
Optional::get
)
)
)
);