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

DongGyu Jung·2022년 5월 5일
0

자바(JAVA)

목록 보기
60/60
post-thumbnail

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

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 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에 해당하는 값들에 대한 처리값이 될 것이다.

🧨 1. 기본 분할

먼저 간단하게 대표적인 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) ; //여학생 리스트

🧨 2. 기본 분할 + 통계정보

위에서 사용했던 각 성별 학생들의 리스트로 나눈 기본 분할 예제를 활용해보자.
우선 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() 결과

🧨 3. 다중 분할

이번에 분할을 두 단계 깊이에 걸쳐 수행하는 것이다.
예를 들면
" 각 성별 그룹에서 성적 기준 합격/불합격으로 분할 "과 같은 작업을 해야한다면
간단하게 할 수 있다.

바로
매개변수 중 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도 분할과 마찬가지로
다중 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
                                        )
                                    )
                                )
                            );

0개의 댓글