시그마 삽질==six 시그마

Stream collect 2탄(그룹핑) 본문

프로그래밍/Java

Stream collect 2탄(그룹핑)

Ethan Matthew Hunt 2020. 8. 27. 21:36

 

해당글은 신용권님의 '이것이 자바다' 를 토대로 작성되었습니다.

예전에 남궁성님의 '자바의 정석' 그리고 신용권님의 '이것이 자바다' 두권 다 보았는데

저는 개인적으로 신용권님의 책이 좀 더 핵심을 찌르고 직관적이어서 좋았던거 같습니다.

책은 요기에서 구매 가능합니다.

 

요소를 그룹핑해서 수집

collect()메소드는 단순히 요소를 수집하는 기능 외에 컬렉션의 요소들을 그룹핑해서 Map객체를 생성하는 기능도 제공한다

Collectors 의 groupingBy() 또는 groupingByConcurrent()가(쓰레드에 안전) 리턴하는 Collector를 매개값으로 대입하면 된다.

 

Collectors.groupingBy() 메소드는 그룹핑 후 매핑이나 집계(평균,카운팅,연결,최대,최소합계)를 할 수있도록 두번째 매개값으로 Collector를 가질 수 있다(두번째 매개값으로 인해  기존 value값이 달라진다 ㅎ)

 

 

collectors 정적메소드 팁: 

 

toMap(
Function<T,K> keyMapper,
Function<T,U> valueMapper)  이든

 

groupingBy(Function<T,K> classifier)이든

 

groupingBy(Function<T,K> classifier,
Collector<T,A,D> collector)이든

 

첫번째 매개변수의 리턴값이 key다.

두번째 매개변수가 있다면 리턴값이  value다

 

 

 

리턴타입 Colllectors의 정적메소드 설명
Collector<T,?,Map<K,List<T>>> groupingBy(Function<T,K> classifier) T를 넣어 K를 리턴

최종적으로 
K가 key인  value T를 얻는다

ex)
람다:T(매개값 -학생)->K(리턴값-성별)

최종리턴:

Key:성별(K)
value:학생(T)
Collector<T,?,ConcurrentMap<K,List<T>>> groupingByConcurrent(
Function<T,K> classifier)
Collector<T,?,Map<K,D>> groupingBy(Function<T,K> classifier,
Collector<T,A,D> collector)
T를 넣어 K를 리턴하고
T를 넣어 D를 리턴한다

최종적으로 
K가 key인 value D를 얻는다

ex)
람다:
T(매개값-학생)->K(리턴값-도시)
T(매개값-학생)->A->D(리턴값-이름)

최종리턴

Key:도시(K)
value:이름(D)
Collector<T,?,ConcurrentMap<K,D>> groupingByConcurrent(
Function<T,K> classifier,
Collector<T,A,D> collector)

 

 

@Data
@AllArgsConstructor
public class Student {

	private String name;
	private int score;
	private Gender gender;
	private City city;

	public Student(String name, int score, Gender gender) {
		this.name = name;
		this.score = score;
		this.gender = gender;
	}
}


@Getter
@AllArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum  Gender {
     MALE("남자","men")
    , FEMALE("여자","woman");

    private String korName;
    private String engName;
}

public enum City {
    Seoul
    , Pusan
}

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class GenderDto {
	private Gender gender;
	private List<Student> students;
}

 

 

 @Test
    public void groupingByExample1() throws Exception{
        //given
        List<Student> totalList = Arrays.asList(
                new Student("공유", 10, Gender.MALE, City.Seoul),
                new Student("김태희", 6, Gender.FEMALE, City.Pusan),
                new Student("원빈", 10, Gender.MALE, City.Pusan),
                new Student("전지현", 6, Gender.FEMALE, City.Seoul)
        );

        //long
        Stream<Student> totalStream = totalList.stream();
        Function<Student,Gender> classifier = Student :: getGender;
        Collector<Student,?,Map<Gender,List<Student>>> collector = Collectors.groupingBy(classifier);
        Map<Gender,List<Student>> mappedByGender=totalStream.collect(collector);


        //short
        Map<Gender, List<Student>> genderMap = totalList.stream()
                //.collect(Collectors.groupingBy(v->v.getGender()));//하단과 같음
                .collect(Collectors.groupingBy(Student :: getGender));
        System.out.println("mapBySex = " + genderMap);
        //mapBySex = {FEMALE=[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)], MALE=[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]}
        System.out.print("[남학생] ");
        
        //v -> v.getGender().getEngName() 뿐만아니라 .v.객체.객체 이렇게 들어가도됨!
        Map<String, List<Student>> collect1 = totalList.stream().collect(Collectors.groupingBy(v -> v.getGender().getEngName()));
        
        LinkedHashMap<Gender, List<Student>> collect2 = totalList.stream().collect(Collectors.groupingBy(it ->
                it.getGender()
            , LinkedHashMap::new
            , Collectors.toList()
        ));


		//forEach key value로 나눠서 보낼 수 있음
        collect2.forEach((gender,StudentList)-> System.out.println("gender:" + gender+"  StudentList:"+StudentList));

		//gender:MALE  StudentList:[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]
		//gender:FEMALE  StudentList:[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]

        genderMap.get(Gender.MALE).stream().forEach(s->System.out.print(s.getName() + " "));
        System.out.print("\n[여학생] ");
        genderMap.get(Gender.FEMALE).stream().forEach(s->System.out.print(s.getName() + " "));
        //[남학생] 공유 원빈
        //[여학생] 김태희 전지현

        //map 사용법1 (key와 해당 value를 람라로 넘김.v.getValue()는 한개key에 해당하는  리스트임!!!)
        List<GenderDto> list = genderMap.entrySet().stream().map(v -> GenderDto.builder().gender(v.getKey()).students(v.getValue()).build())
            .collect(Collectors.toList());
        System.out.println("\nlist = " + list);
        //list = [GenderDto(gender=FEMALE, students=[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]), GenderDto(gender=MALE, students=[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)])]

        genderMap.entrySet()/*.stream()*/.forEach(v->System.out.println("\n student = " + v));
        //key 와 value 묶여서 출력됨
        // student = FEMALE=[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]
        // student = MALE=[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]

        //map 사용법2(key마다 해당 value만 넘김!! key는 포)
        List<GenderDto> list1 = genderMap.values().stream().map(v -> {
                System.out.println("v="+v);
            return GenderDto.builder().gender(Gender.FEMALE).students(v).build();
        }).collect(Collectors.toList());
        
        System.out.println("\nlist1 = " + list1);
        //v=[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]
        //v=[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]
        //list1 = [GenderDto(gender=FEMALE, students=[Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]), GenderDto(gender=FEMALE, students=[Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)])]

        genderMap.values()/*.stream()*/.forEach(v-> System.out.println("\n student2 = " + v));
        //key 에해당하는 value들만 묶여서 출력됨
        // student2 = [Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul)]
        // student2 = [Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]

        //map 사용법3감(전부 flat시킴)
        List<Student> collect = genderMap.values().stream().flatMap(v -> v.stream()).collect(Collectors.toList());
        System.out.println("collect = " + collect);  //1개씩 결과가 출력
        //collect = [Student(name=김태희, score=6, gender=FEMALE, city=Pusan), Student(name=전지현, score=6, gender=FEMALE, city=Seoul), Student(name=공유, score=10, gender=MALE, city=Seoul), Student(name=원빈, score=10, gender=MALE, city=Pusan)]


        //map 사용법4( 두 방법으로 사용가능, 그냥사용 or flat)
        for(Entry<Gender, List<Student>> entry: genderMap.entrySet()){
            final Gender gender = entry.getKey();
            final List<Student> students = entry.getValue();
            entry.getValue().stream().forEach(v-> System.out.println("map2:"+v));
        }
        //map2:Student(name=김태희, score=6, gender=FEMALE, city=Pusan)
        //map2:Student(name=전지현, score=6, gender=FEMALE, city=Seoul)
        //map2:Student(name=공유, score=10, gender=MALE, city=Seoul)
        //map2:Student(name=원빈, score=10, gender=MALE, city=Pusan)

    }

 

 

 

//하단의 예제는 다음에 이어지는 'Stream collect 3탄(그룹핑후 매핑 및 집계)' 를 봐야 이해가된다

   @Test
    public void groupingByExample2() throws Exception{
        //given
        List<Student> totalList = Arrays.asList(
                new Student("공유", 10, Gender.MALE, City.Seoul),
                new Student("김태희", 6, Gender.FEMALE, City.Pusan),
                new Student("원빈", 10, Gender.MALE, City.Pusan),
                new Student("전지현", 6, Gender.FEMALE, City.Seoul)
        );

        //long
        Stream<Student> totalStream = totalList.stream();
        Function<Student,City> classifier = Student :: getCity;
        Function<Student,String> mapper= Student::getName;

        Collector<String,?,List<String>> collector1= Collectors.toList();
        Collector<Student,?,List<String>> collector2= Collectors.mapping(mapper,collector1);

        Collector<Student,?,Map<City,List<String>>> collector3 = Collectors.groupingBy(classifier,collector2);
        Map<City,List<String>> mappedByCity=totalStream.collect(collector3);

       //short
        Map<City, List<String>> mapByCity = totalList.stream()
                .collect(
                        Collectors.groupingBy(
                                Student::getCity,
                                Collectors.mapping(Student::getName, Collectors.toList())
                        )
                );
        System.out.println("mapByCity = " + mapByCity);
        System.out.print("\n[서울] ");
        mapByCity.get(City.Seoul).stream().forEach(s->System.out.print(s + " "));
        System.out.print("\n[부산] ");
        mapByCity.get(City.Pusan).stream().forEach(s->System.out.print(s + " "));
    }

 

 

결과

mapByCity = {Seoul=[공유, 전지현], Pusan=[김태희, 원빈]}

[서울] 공유 전지현
[부산] 김태희 원빈

 

 

   @Test
    public void groupingByExample0() throws Exception{
        List<Student> totalList = Arrays.asList(
            new Student("공유", 10, Gender.MALE, City.Seoul),
            new Student("김태희", 6, Gender.FEMALE, City.Pusan),
            new Student("원빈", 10, Gender.MALE, City.Pusan),
            new Student("전지현", 6, Gender.FEMALE, City.Seoul)
        );

        Map<Gender, Map<String, Map<String, List<Student>>>> collect = totalList.stream()
            .collect(Collectors.groupingBy(Student::getGender, Collectors.groupingBy(v -> v.getGender().getKorName(),
                Collectors.groupingBy(v -> v.getGender().getEngName()))));
        String s = new ObjectMapper().writeValueAsString(collect);
        System.out.printf( "JSON: %s", s);
    }


{
  "MALE": {
    "남자": {
      "men": [
        {
          
          "name": "공유",
          "score": 10,
          "gender": {
            "korName": "남자",
            "engName": "men",
            "code": "MALE",
            "male": true
          },
          "city": "Seoul"
        },
        {
         
          "name": "원빈",
          "score": 10,
          "gender": {
            "korName": "남자",
            "engName": "men",
            "code": "MALE",
            "male": true
          },
          "city": "Pusan"
        }
      ]
    }
  },
  "FEMALE": {
    "여자": {
      "woman": [
        {
          "name": "김태희",
          "score": 6,
          "gender": {
            "korName": "여자",
            "engName": "woman",
            "code": "FEMALE",
            "male": false
          },
          "city": "Pusan"
        },
        {
          "name": "전지현",
          "score": 6,
          "gender": {
            "korName": "여자",
            "engName": "woman",
            "code": "FEMALE",
            "male": false
          },
          "city": "Seoul"
        }
      ]
    }
  }
}
Comments