본문 바로가기
java

Lambda 07 Collector

by 킹차니 2021. 12. 30.

 

collect()와 Collectors

  • collect()는 Collector를 매개변수로 하는 스트림의 최종 연산
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)//잘 안쓰임

reduce() : 리듀싱

collect() : 그룹별 리듀싱

 

Collector는 수집(collect)에 필요한 메소드를 정의해 놓은 인터페이스

public interface Collector<T, A, R> { // T(요소)를 A에 누적한 다음, 결과를 R로 변환해서 반환
	Supplier<A> supplier(); //StringBuilder::new 누적할 곳
	BiConsumer<A, T> accumulator(); //(sb, s) -> sb.append(s) 누적방법
	BinaryOperator<A> combiner(); //(sb1, sb2) -> sb1.append(sb2) 결합방법(병렬)
	Function<A, R> finisher(); //sb->sb.toString() 최종변환
	Set<Characteristics> characterstics(); //컬렉터의 특성이 담긴 Set을 반환
	...
}

 

Collectors클래스는 다양한 기능의 컬렉터(Collector를 구현하는 클래스)를 제공

변환 - mapping(), toList(), toSet(), toMap(), toCollection()
통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt(),
문자열 결합 - joining()
리듀싱 - reducing()
그룹화와 분할 - groupingBy(), partitioningBy(), collectingAndThen()

 

스트림을 컬렉션, 배열로 변환 - toList(), toSet(), toMap(), toCollection()

//Stream<Student> -> Stream<String>
List<String> names = studentStream.map(Student::getName).collect(Collectors.toList());

//Stream<String> -> ArrayList<String>
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));

//Stream<Person> -> Map<String, Person>
Map<String, Person> map = personStream.collect(Collectors.toMap(p->p.getId(), p->p));

 

스트림을 배열로 변환 - toArray()

Student[] stuNames = studentStream.toArray(Student[]::new); // OK
Student[] stuNames = studentStream.toArray() // ERROR. 자동 형변환 안된다.
Object[] stuNames = studentStream.toArray(); // OK

 

스트림의 통계 - counting(), summingInt(), maxBy(), minBy()... : 그룹별 통계가 가능

long count = stuStream.count();
long count = stuStream.collect(couting()); // Collectors.counting()

long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); //IntStream의 sum()
long totalScore = stuStream.collect(summingInt(Student::getTotalScore));

 

스트림을 리듀싱 - reducing() : 그룹별 리듀싱이 가능

Collector reducing(BunaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
Collector reduing(U identity, Function<T, U> mapper, BunaryOperator<U> op) //map+reduce
IntStream intStream = new Random().ints(1, 46).distinct().limit(6);

OptionalInt max = intStream.reduce(Integer::max); // 전체 리듀싱
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max));
long sum = intStream.reduce(0, (a,b) -> a + b);
long sum = intStream.boxed().collect(reducing(0,(1,b)->a+b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));

 

문자열 스트림의 요소를 모두 연결 - joining()

String studentNames = stuStream.map(Student::getName).collect(joining());
String studentNames = stuStream.map(Student::getName).collect(joining(","));
String studentNames = stuStream.map(Student::getName).collect(joining(",", "[", "]");
String studentInfo = stuStream.collect(joining(","));//Student의 toString()으로 결합

 


 

스트림의 그룹화와 분할

patitioningBy()는 스트림을 2분할하고, groupingBy()는 스트림을 n분할한다.

Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)

Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downStream)

 

partitioningBy()

Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale));//학생들을 성별로 분할
List<Student> maleStudent = stuBySex.get(true); //Map에서 남학생 목록을 얻는다.
List<Student> femaleStudent = stuBySex.get(false); //Map에서 여학생 목록을 얻는다.
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting()));//분할+통계
System.out.println("남학생 수 :"+stuNumBySex.get(true);//남학생 수
System.out.println("남학생 수 :"+stuNumBySex.get(false);//여학생 수
Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect(partitiongBy(Student::isMale, maxBy(comparingInt(Student::getScore))));//분할+통계
System.out.println("남학생 1등 : "+topScoreBySex.get(true));//남학생 1등 :Optional[[나자바, 남, 1, 1, 300]]
System.out.println("여학생 1등 : "+topScoreBySex.get(false));//남학생 1등 :Optional[[김지미, 여, 1, 1, 300]]
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex 
= stuStream.collect(partitioningBy(Student::isMale, // 1. 성별로 분할(남/녀)
										partitioningBy(s->s.getScore()<150))); // 2. 성적으로 분할(불합격/합격)
List<Student> failedMaleStu = failedStuBySex.get(true).get(true);//남+불합격
List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);//여+불합격

 

groupingBy()

Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downStream)
Map<Integer, List<Student>> stuByBan = stuStream //학생을 반별로 그룹화
						.collect(groupingBy(Student::getBan, toList())); //toList() 생략가능
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream //다중 그룹화
						.collect(groupingBy(Student::getHak,    // 1. 학년별 그룹화
										 groupingBy(Student::getBan))); // 2. 반별 그룹화

 

 

예시 코드보기

 

Student 클래스

public class Student {
    String name;
    boolean isMale;
    int hak;
    int ban;
    int score;
    public Student(String name, boolean isMale, int hak, int ban, int score) {
        this.name = name;
        this.isMale = isMale;
        this.hak = hak;
        this.ban = ban;
        this.score = score;
    }
    boolean isMale(){return isMale;}
    @Override
    public String toString() {
        return String.format("[%s, %s, %d학년 %d반, %3d점]", name, isMale?"남":"여", hak, ban, score);
    }

    public String getName() {return name;}
    public int getHak() {return hak;}
    public int getBan() {return ban;}
    public int getScore() {return score;}

    //groupingBy에 사용
    enum Level{HIGH, MID, LOW}//성적을 상,중,하로 구분
}

 

EX1 partitioningBy()

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;

public class Ex1413 {
    public static void main(String[] args) {
        Student[] stuArr = {
                new Student("김자바", true, 1, 1, 270),
                new Student("강자바", false, 2, 1, 100),
                new Student("이자바", true, 1, 1, 130),
                new Student("박자바", false, 1, 1, 200),
                new Student("황자바", true, 2, 1, 190),
                new Student("정자바", false, 1, 2, 300),
                new Student("연자바", true, 2, 2, 110),
                new Student("최자바", false, 2, 2, 230),
                new Student("선자바", true, 1, 2, 240),
                new Student("장자바", false, 2, 2, 90),
                new Student("왕자바", true, 2, 3, 100),
                new Student("금자바", false, 1, 3, 280),
                new Student("별자바", true, 1, 3, 190),
                new Student("무자바", false, 2, 3, 200),
                new Student("유자바", true, 1, 3, 210),
        };

        System.out.println("1. 단순분할 (성별로 분할)");
        Stream<Student> stuStream1 = Stream.of(stuArr);
        Map<Boolean, List<Student>> stuBySex = stuStream1.collect(partitioningBy(Student::isMale));
        List<Student> boys = stuBySex.get(true);
        List<Student> girls = stuBySex.get(false);
        for (Student boy : boys) System.out.println(boy+" ");
        for (Student girl : girls) System.out.println(girl+" ");

        System.out.println("------------------------------------------------------");

        System.out.println("2. 단순분할 + 통계(성별 학생수)");
        Map<Boolean, Long> stuNumBySex = Stream.of(stuArr).collect(partitioningBy(s -> s.isMale(), counting()));
        System.out.println("남학생 수: "+ stuNumBySex.get(true));//남학생 수
        System.out.println("여학생 수: "+stuNumBySex.get(false));//여학생 수

        System.out.println("------------------------------------------------------");
        System.out.println("3. 단순분할 + 통계(성별1등)");
        Map<Boolean, Optional<Student>> topScoreBySex1 = Stream.of(stuArr).collect(partitioningBy(Student::isMale,
                maxBy(comparingInt(Student::getScore))));
        System.out.println("남자 1등 : " + topScoreBySex1.get(true));
        System.out.println("여자 1등 : " + topScoreBySex1.get(false));

        Map<Boolean, Student> topScoreBySex2 = Stream.of(stuArr).collect(partitioningBy(Student::isMale,
                collectingAndThen(maxBy(comparingInt(Student::getScore)), Optional::get)));//Optioanl.get을 하면 Optional에서 꺼낸 값을 준다.
        System.out.println("남자 1등 : " + topScoreBySex2.get(true));
        System.out.println("여자 1등 : " + topScoreBySex2.get(false));

        System.out.println("------------------------------------------------------");
        System.out.println("4. 다중분할(성별+불합격자(100점이하))");
        Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = Stream.of(stuArr).collect(partitioningBy(Student::isMale, partitioningBy(s -> s.getScore() <= 100)));
        List<Student> failedMaleStu = failedStuBySex.get(true).get(true);
        List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);
        for (Student student : failedMaleStu) System.out.println(student);
        for (Student student : failedFemaleStu) System.out.println(student);
    }
}



실행결과:

1. 단순분할 (성별로 분할)
[김자바, 남, 1학년 1반, 270점] 
[이자바, 남, 1학년 1반, 130점] 
[황자바, 남, 2학년 1반, 190점] 
[연자바, 남, 2학년 2반, 110점] 
[선자바, 남, 1학년 2반, 240점] 
[왕자바, 남, 2학년 3반, 100점] 
[별자바, 남, 1학년 3반, 190점] 
[유자바, 남, 1학년 3반, 210점] 
[강자바, 여, 2학년 1반, 100점] 
[박자바, 여, 1학년 1반, 200점] 
[정자바, 여, 1학년 2반, 300점] 
[최자바, 여, 2학년 2반, 230점] 
[장자바, 여, 2학년 2반,  90점] 
[금자바, 여, 1학년 3반, 280점] 
[무자바, 여, 2학년 3반, 200점] 
------------------------------------------------------
2. 단순분할 + 통계(성별 학생수)
남학생 수: 8
여학생 수: 7
------------------------------------------------------
3. 단순분할 + 통계(성별1등)
남자 1등 : Optional[[김자바, 남, 1학년 1반, 270점]]
여자 1등 : Optional[[정자바, 여, 1학년 2반, 300점]]
남자 1등 : [김자바, 남, 1학년 1반, 270점]
여자 1등 : [정자바, 여, 1학년 2반, 300점]
------------------------------------------------------
4. 다중분할(성별+불합격자(100점이하))
[왕자바, 남, 2학년 3반, 100점]
[강자바, 여, 2학년 1반, 100점]
[장자바, 여, 2학년 2반,  90점]

 

EX2 groupingBy() )

import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

public class Ex1414 {
    public static void main(String[] args) {
        Student[] stuArr = {
                new Student("김자바", true, 1, 1, 270),
                new Student("강자바", false, 2, 1, 100),
                new Student("이자바", true, 1, 1, 130),
                new Student("박자바", false, 1, 1, 200),
                new Student("황자바", true, 2, 1, 190),
                new Student("정자바", false, 1, 2, 300),
                new Student("연자바", true, 2, 2, 110),
                new Student("최자바", false, 2, 2, 230),
                new Student("선자바", true, 1, 2, 240),
                new Student("장자바", false, 2, 2, 90),
                new Student("왕자바", true, 2, 3, 100),
                new Student("금자바", false, 1, 3, 280),
                new Student("별자바", true, 1, 3, 190),
                new Student("무자바", false, 2, 3, 200),
                new Student("유자바", true, 1, 3, 210),
        };
        System.out.println("1. 단순 그룹화(반별로 그룹화)");
        Map<Integer, List<Student>> stuByBan = Stream.of(stuArr).collect(groupingBy(Student::getBan));
        for (List<Student> bans : stuByBan.values())
            for (Student s : bans)
                System.out.println(s);

        System.out.println("--------------------------------------");

        System.out.println("2. 단순 그룹화(성적별로 그룹화)");
        Map<Student.Level, List<Student>> stuByLevel = Stream.of(stuArr).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;
        }));

        TreeSet<Student.Level> keySet = new TreeSet<>(stuByLevel.keySet());
        for (Student.Level level : keySet) {
            System.out.println(level);
            for (Student s : stuByLevel.get(level))
                System.out.println(s);
            System.out.println();
        }

        System.out.println("--------------------------------------");

        System.out.println("3. 단순 그룹화 + 통계(성적별 학생수)");
        Map<Student.Level, Long> stuCntByLevel = Stream.of(stuArr).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()));
        for (Student.Level level : stuCntByLevel.keySet())
            System.out.printf("[%s]-%d명, ", level, stuCntByLevel.get(level));
        System.out.println();
    }
}


실행결과:

1. 단순 그룹화(반별로 그룹화)
[김자바, 남, 1학년 1반, 270점]
[강자바, 여, 2학년 1반, 100점]
[이자바, 남, 1학년 1반, 130점]
[박자바, 여, 1학년 1반, 200점]
[황자바, 남, 2학년 1반, 190점]
[정자바, 여, 1학년 2반, 300점]
[연자바, 남, 2학년 2반, 110점]
[최자바, 여, 2학년 2반, 230점]
[선자바, 남, 1학년 2반, 240점]
[장자바, 여, 2학년 2반,  90점]
[왕자바, 남, 2학년 3반, 100점]
[금자바, 여, 1학년 3반, 280점]
[별자바, 남, 1학년 3반, 190점]
[무자바, 여, 2학년 3반, 200점]
[유자바, 남, 1학년 3반, 210점]
--------------------------------------
2. 단순 그룹화(성적별로 그룹화)
HIGH
[김자바, 남, 1학년 1반, 270점]
[박자바, 여, 1학년 1반, 200점]
[정자바, 여, 1학년 2반, 300점]
[최자바, 여, 2학년 2반, 230점]
[선자바, 남, 1학년 2반, 240점]
[금자바, 여, 1학년 3반, 280점]
[무자바, 여, 2학년 3반, 200점]
[유자바, 남, 1학년 3반, 210점]

MID
[강자바, 여, 2학년 1반, 100점]
[이자바, 남, 1학년 1반, 130점]
[황자바, 남, 2학년 1반, 190점]
[연자바, 남, 2학년 2반, 110점]
[왕자바, 남, 2학년 3반, 100점]
[별자바, 남, 1학년 3반, 190점]

LOW
[장자바, 여, 2학년 2반,  90점]

--------------------------------------
3. 단순 그룹화 + 통계(성적별 학생수)
[MID]-6명, [LOW]-1명, [HIGH]-8명,

 

 

출처: 남궁성님 유튜브 강의
https://www.youtube.com/user/MasterNKS

'java' 카테고리의 다른 글

Generics 02 제네릭스 용어, 제네릭 타입과 다형성  (0) 2022.01.02
Generics 01 제네릭스란?  (0) 2021.12.31
Optional  (0) 2021.12.30
Lambda 06 Stream의 연산 (중간 연산과 최종 연산)  (0) 2021.12.29
Lambda 05 Stream  (0) 2021.12.27