프로그래밍 언어/Java

[Java] 자바 스트림 collect() 정리 및 사용 예제

happy_life 2022. 7. 6. 20:18

[Java] 자바 스트림 collect() 정리 및 사용 예제

 

 

collect() 개요

스트림의 최종 연산자 중 가장 복잡하지만, 가장 유용하게 활용될수 있는 것이 바로 collect()이다. collect() 또한 reduce()와 유사하다. 하지만 추가적으로 어떻게 수집할 것인가에 대한 방법이 정의되어 있는데, 이를 구현하는 것이 collector 이다.

collector는 Collector 인터페이스를 구현한 것이고, 직접 구현할 수도 있지만, 미리 작성된 것을 사용하기도 한다. Collectors 클래스는 미리 작성된 다양한 collector를 반환하는 static 메서드를 가지고 있다.

구현된 collector를 collect() 메서드의 인자로 넣어 사용한다. 

 

collect() 스트림의 최종연산, 매개변수로 컬렉터를 필요로 한다.
Collector 인터페이스로 컬렉터는 이를 구현해야 한다.
Collectors 클래스로 static 메서드로 미리 구현한 컬렉터를 제공한다.

 

 

스트림을 컬렉션이나 배열로 반환하는 방법

컬렉션으로 반환

List로 변환 Collectors.toList()
Map으로 변환 Collectors.toMap()
그 외 Collections로 변환 Collectors.toCollection(람다식)

 

코드 예제

//Collectors.toList()
Stream<String> stream = Stream.of("1", "2", "3", "4", "5");
List<String> streamToList = stream.collect(Collectors.toList());
System.out.println("streamToList = " + streamToList);

//Collectors.toMap()
Stream<String> stream2 = Stream.of("1", "2", "3", "4", "5");
Map<Integer, String> streamToMap = stream2.collect(Collectors.toMap((i) -> Integer.parseInt(i), (i) -> "\""+(i)+"\""));
System.out.println("streamToMap = " + streamToMap);

//List나 Map이 아닌 컬렉션으로의 변환
Stream<String> stream3 = Stream.of("1", "2", "3", "4", "5");
ArrayList<String> streamToArrayList = stream3.collect(Collectors.toCollection(() -> new ArrayList<>()));
System.out.println("streamToArrayList = " + streamToArrayList);

 

배열로 반환

Collect()는 아니지만, toArray를 사용한다.

 

코드 예제

// 배열로 변환
Stream<String> stream4 = Stream.of("1", "2", "3", "4", "5");
Object[] objects = stream4.toArray();

Stream<String> stream5 = Stream.of("1", "2", "3", "4", "5");
String[] stringArray = stream5.toArray((i) -> new String[i]);

특정한 형을 원할 때에는 인자에 람다식을 넣어주어야 한다.

 

728x90

 

통계

Stream의 메소드도 있지만, collect를 사용하면 나중에 그룹별로 묶을 수 있다. 이런 유용함 때문에 collect로 통계를 사용할 수 있도록 만들어져 있다.

 

counting()

Stream의 개수를 센다.

 

코드 예제

 

//count - 기존
Stream<Integer> Stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7);
long count = Stream1.count();
System.out.println("count = " + count);

//counting - collect
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Long count2 = stream2.collect(Collectors.counting());
System.out.println("count2 = " + count2);

 

 

summingxx()

값을 더한다

 

코드 예제

//sum - 기존
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6, 7);
int sum1 = stream3.mapToInt(i -> i).sum();
System.out.println("sum1 = " + sum1);

//summingInt - collect
Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Integer sum2 = stream4.collect(Collectors.summingInt(i -> i));
System.out.println("sum2 = " + sum2);

 

 

maxBy() , minBy()

큰 것 혹은 작은 것을 반환한다.

 

코드 예제

//max - 기존
Stream<Integer> stream5 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Optional<Integer> max = stream5.max(Comparator.comparingInt(i -> i));
System.out.println("max = " + max);

Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Optional<Integer> max2 = stream6.collect(Collectors.maxBy(Comparator.comparingInt(i -> i)));
System.out.println("max2 = " + max2);

 

 

reducing()

collect의 reducing를 기존의 reduce()처럼 사용할 수 있다.

 

코드 예제

//reduce - 기존
Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Integer val1 = stream7.reduce(0, (a, b) -> a + b);
System.out.println("val1 = " + val1);

//reducing - collect
Stream<Integer> stream8 = Stream.of(1, 2, 3, 4, 5, 6, 7);
Integer val2 = stream8.collect(Collectors.reducing(0, (a, b) -> a + b));
System.out.println("val2 = " + val2);

 

 

 

문자열 결합

문자열 결합은 joining()이라는 것을 사용한다.

 

코드 예제

// 매개변수가 1개
Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6");
String joining = stream.collect(Collectors.joining(","));
System.out.println("joining = " + joining);

// 매개변수가 3개
Stream<String> stream2 = Stream.of("1", "2", "3", "4", "5", "6");
String joining2 = stream2.collect(Collectors.joining(",", "[", "]"));
System.out.println("joining2 = " + joining2);

출력 결과

 

 

 

그룹화와 분할 - groupingBy(), partitioningBy()

Stream에서 이미 제공하고 있는 메서드들을 왜 collect에 또 추가해주었을까? 바로 그룹화 때문이다. stream의 메서드와 달리 collect의 메서드는 그룹화를 통해 각각의 그룹에 조건을 지정해줄 수 있다.

 

partitioningBy()

그룹을 true와 false 두 개로 분리한다.

 

 

아래의 코드 예제는 import static으로 Collectors 코드를 간단하게 줄였음

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

 

코드 예제

public class CollectEx4 {
    public static void main(String[] args) {
        Member[] memberList = {
                new Member("김수박", true, 1000),
                new Member("최수박", true, 2000),
                new Member("이수박", false, 100),
                new Member("류수박", true, 2500),
                new Member("상수박", false, 300),
                new Member("강수박", true, 5000)
        };

        /**
         * 기본 사용 예제
         */
        // stream으로 변환
        Stream<Member> memberStream = Stream.of(memberList);

        //partitionBy로 구분하기 vip인 사람과 아닌 사람
        Map<Boolean, List<Member>> isVipMap = memberStream.collect(partitioningBy((Member member) -> member.isVip));

        //key로 꺼내기
        List<Member> vipList = isVipMap.get(true);
        List<Member> notVipList = isVipMap.get(false);
        System.out.println("vip인 사람과 아닌 사람");
        // 요소 출력
        for (Member vip : vipList) {
            System.out.println("vip = " + vip);
        }
        for (Member notVip : notVipList) {
            System.out.println("notVip = " + notVip);
        }

        /**
         * Collector 추가 사용 예제
         */
        //stream으로 변환
        Stream<Member> memberStream2 = Stream.of(memberList);

        //partitionBy로 구분하기 vip인 사람과 아닌 사람의 수
        Map<Boolean, Long> longMap = memberStream2.collect(partitioningBy((Member member) -> member.isVip, counting()));
        System.out.println("vip인 사람과 아닌 사람의 수");
        System.out.println("vip 수: " + longMap.get(true));
        System.out.println("notVip 수: " + longMap.get(false));

        /**
         * partition 안에 partiton
         */
        //stream으로 변환
        Stream<Member> memberStream3 = Stream.of(memberList);

        //partition 안에 partition vip이면서 point가 3000 미만인 사람
        Map<Boolean, Map<Boolean, List<Member>>> pInP = memberStream3.collect(partitioningBy((Member m) -> m.isVip,
                                                                              partitioningBy((Member s) -> s.point < 3000)));
        List<Member> members = pInP.get(true).get(true);
        System.out.println("vip이면서 point가 3000 미만인 사람");
        for (Member member : members) {
            System.out.println("member = " + member);
        }
    }
}

class Member {
    String name;
    boolean isVip;
    long point;

    public Member(String name, boolean isVip, long point) {
        this.name = name;
        this.isVip = isVip;
        this.point = point;
    }

    @Override
    public String toString() {
        return "Member{" +
                "name='" + name + '\'' +
                ", isVip=" + isVip +
                ", point=" + point +
                '}';
    }
}//Member

 

출력 결과

 

 

pInP의 구조는 아래와 같다.

 

pInP의 구조

 

 

 

groupingBy()

Stream을 어떤 기준을 바탕으로 n분할 하는 것이 groupBy()이다. 사용방법은 partitioningBy()와 유사하다.

 

코드 예제

public class CollectEx5 {
    public static void main(String[] args) {
        Student[] studentArr = {
                new Student("김수박", 1, 5, 100),
                new Student("최수박", 1, 1, 200),
                new Student("이수박", 1, 2, 300),
                new Student("명수박", 2, 3, 400),
                new Student("맹수박", 2, 6, 50),
                new Student("시수박", 2, 1, 250),
                new Student("계수박", 3, 2, 350),
                new Student("남궁수박", 3, 3, 450)
        };


        System.out.println("--단순하게 학년 별로 그룹화 하기--");
        Stream<Student> stream1 = Stream.of(studentArr);
        Map<Integer, List<Student>> stuByYear = stream1.collect(groupingBy((Student s) -> s.getYear()));
        for (Integer key : stuByYear.keySet()) {
            System.out.println(key + "학년: " + stuByYear.get(key));
        }//끝

        System.out.println();
        System.out.println("--성적별로 그룹화 하기--");
        Stream<Student> stream2 = Stream.of(studentArr);
        Map<Student.Level, List<Student>> stuByLevel = stream2.collect(groupingBy((Student s) -> {
            if (s.getScore() >= 200)      return Student.Level.HIGH;
            else if (s.getScore() >= 100) return Student.Level.MID;
            else                          return Student.Level.LOW;
        }));

        for (Student.Level key : stuByLevel.keySet()) {
            System.out.println("[" + key + "]");
            for (Student s : stuByLevel.get(key)) {
                System.out.println("학생 = " + s);
            }
            System.out.println();
        }//끝

        System.out.println();
        System.out.println("--다중 그룹화 하기 학년 && 반--");
        Stream<Student> stream3 = Stream.of(studentArr);
        Map<Integer, Map<Integer, List<Student>>> stuByYearClassNum = stream3.collect(groupingBy(s->s.getYear(), groupingBy(s->s.getClassNum())));

        for (Integer year : stuByYearClassNum.keySet()) {
            System.out.println("[" + year + "학년" + "]");

            Map<Integer, List<Student>> integerListMap = stuByYearClassNum.get(year);

            for (Integer classNum : integerListMap.keySet()) {
                System.out.println(classNum + "반 " + integerListMap.get(classNum));
            }
            System.out.println();
        }
    }
}

class Student {
    String name;
    int year; // 학년
    int classNum; // 반
    int score; // 점수

    public Student(String name, int year, int classNum, int score) {
        this.name = name;
        this.year = year;
        this.classNum = classNum;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getYear() {
        return year;
    }

    public int getClassNum() {
        return classNum;
    }

    public int getScore() {
        return score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", year=" + year +
                ", classNum=" + classNum +
                ", score=" + score +
                '}';
    }

    enum Level {HIGH, MID, LOW}
}

 

단순하게 학년별로 그룹화 하기 출력

 

 

성적별로 그룹화 하기 출력

 

 

다중 그룹화 하기 학년 && 반 출력

 

 

지금까지 collect의 다양한 쓰임에 대해 알아보았다.