ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다와 스트림 - collect() part1
    백엔드/자바 2023. 4. 17. 06:45

    collect()란?

    collect()란 스트림의 최종 연산 중 하나의 과정이다.

     

    사실 다른 최종 연산과 같이 다뤄야하나 비중이 크다보니 별도로 다룰 것이다.

     

    collect()는 간단히 말하면 다양한 콜렉션 자료들을 쉽게 가공할 수 있게 도와주는 메서드다.

     

    collect()와 collectors

    collect()는 이전에 배운 reduce()가 가진 기능 뿐 아니라 더욱 다양한 기능을 가진다.

     

    그런데 앞서 reduce()에선 직접 연산 과정을 다 적어줬다.

     

    하지만 collect()의 경우 더욱 복잡하고 다양한 자료 가공을 하다보니 직접 적는건 힘들다.

     

    그렇다보니 collect()를 편하게 사용하기 위해 필요한 것이 바로 collectors 클래스다.

     

    static 클래스인 collectors는 다양한 컬렉션(데이터)를 가공할 수 있는 메서드를 가졌다.

     

    그래서 collect()의 매개변수로 collectors가 가진 메서드를 전달하는 식으로 데이터를 쉽게 가공할 수 있다.

     

    collect() 실습에 사용할 클래스

    class Students implements Comparable{
        enum level {High, Middle, Low}
    
        int num;
        String name;
        boolean gender;
        int score;
        int ban;
    
        Students(){};
        Students(int ban, int num, String name, boolean gender, int score){
            this.ban = ban;
            this.num = num;
            this.name = name;
            this.gender = gender;
            this.score = score;
        };
    
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "반: "+this.ban+" 번호: "+this.num+" 이름: "+this.name+" 성별: "+this.gender+" 점수: "+this.score;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Students) {
                Students stu1 = (Students)obj;
                Students stu2 = this;
                return stu1.ban == stu1.ban && stu1.name == stu2.name && stu1.num == stu2.num && stu1.gender == stu2.gender && stu1.score == stu2.score;
            }
            return false;
        }
    
    
        public int getNum() {
            return num;
        }
    
        public String getName() {
            return name;
        }
    
        public boolean isGender() {
            return gender;
        }
    
        public int getScore() {
            return score;
        }
    
        public int getBan() {
            return ban;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(ban+num+name+gender+score);
        }
    
        @Override
        public int compareTo(Object o) {
            if(o instanceof Students) {
                Students stu1 = (Students) o;
                Students stu2 = this;
                return (stu1.num)-(stu2.num);
            }
            return 0;
        }
    
    }
    public static void main(String[] args) {
    
            List<Students> stuList = new ArrayList<Students>();
    
            stuList.add(new Students(1, 5, "권오번", true, 70));
            stuList.add(new Students(2, 4, "윤사번", true, 80));
            stuList.add(new Students(3, 2, "정이번", false, 90));
            stuList.add(new Students(1, 7, "나칠번", true, 83));
            stuList.add(new Students(2, 1, "하일번", true, 88));
            stuList.add(new Students(3, 3, "정삼번", false, 75));
            stuList.add(new Students(2, 6, "차육번", false, 65));
     }

     

    collect()의 경우 기능이 굉장히 많아서 우선 실습에 앞서 사용할 클래스를 미리 만들어두고 하는게 좋다.

     

    해당 글은 위의 코드를 기반으로 진행할 것이다.

     

    대략적으로 클래스를 설명하자면 학생들의 반, 번호, 이름, 성별, 시험점수를 멤버변수로 갖는 클래스다.

     

    그리고 아래 코드는 클래스의 객체를 구현해서 학생 리스트에 넣는 코드다.

     

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

    toList() : 스트림을 리스트로

     

     

    우선 collect()에서 가장 많이 사용하게 되는 기능은 바로 스트림 요소를 리스트로 변환하여 전달하는 기능이다.

     

    사용법은 위와 같다. 이전 단락에서 말한대로 스트림을 생성 후 collect()를 이용하면 된다.

     

    이때 collect() 메서드의 인수로 collectors 클래스의 toList() 메서드를 전달하기만 하면 된다.

     

     

     

    그렇게하면 위 그림과 같이 이름만 따로 추출한 리스트를 얻어낼 수 있다.

     

     

    toSet() : 스트림을 셋으로

     

     

    toSet()의 사용법은 toList()와 동일하다.

     

    위에서 볼 수 있듯이 toSet()도 collect()의 인수로 전달되기만 하면 된다.

     

     

     

    그리고 순서는 다르지만 중복되는 이름이없기에 List와 동일하게 이름이 출력된 것을 볼 수 있다.

     

     

    toMap() : 스트림을 맵으로

     

     

    toMap()의 경우 리스트, 셋보다는 조금 더 까다롭다.

     

    그럴 수 밖에 없는게 Map의 경우 key, value 한쌍의 데이터가 필요하기 때문이다.

     

    이 경우 toList(), toSet()과 달리 toMap()은 데이터를 toMap() 메서드 안에서 가공하게 된다.

     

    toMap()에는 두 개의 람다식이 들어가는데, 위와 같이 원하는 객체의 자료를 람다식으로 추출하면 된다.

     

    이 경우는 학생의 번호를 key로, 이름을 value로 만들어 Map의 형태로 반환한 것이다.

     

     

     

    결과는 위와 같다. Map의 형태로 번호, 이름이 묶여서 출력된 것을 확인할 수 있다.

     

     

    toArray() : 스트림을 배열로

     

     

    스트림을 배열로 바꾸는건 collect()를 사용하는건 아니지만 배워보자.

     

    스트림을 배열로 반환하는건 얼핏보면 collect()보다 쉬워보인다.

     

    스트림을 만들고 바로 toArray()를 사용하면 되기 때문이다.

     

     

     

    하지만 주의할 점이 하나 있는데, 바로 toArray()의 기본 반환 타입이 Object라는 것이다.

     

    위 그림의 stuArr3를 보면 스트림을 배열로 만들 때는 굳이 매개변수가 필요가 없다는 것을 알 수 있다.

     

    stuArr3를 만든 것처럼 toArray()를 통해 바로 배열 생성이 가능하다.

     

    하지만 이렇게 매개변수 없이 배열을 생성할 경우 Object타입으로 자료를 반환한다.

     

    그렇기때문에 별도의 처리가 필요없이 stuArr1처럼 배열의 생성자를 매개변수로 지정해두면 편하다.

     

    이 경우 위 그림에서 볼 수 있듯 별도의 처리 없이 편리하게 배열을 만들 수 있기 때문이다.

     

     

     

    스트림을 배열로 바꾼 결과는 위와 같다.

     

    Student 객체가 배열에 순서대로 저장된 것을 확인할 수 있다.

     

    스트림의 통계 구하기 - counting(), summingInt(), maxBy(), minBy()

    counting(): 데이터의 갯수 구하기

     

     

    사실 스트림에는 요소의 개수를 쉽게 구할 수 있는 메서드가 있다.

     

    바로 count()다. 이건 그냥 collect()도 필요없고 스트림을 만든다면 바로 사용할 수 있다.

     

    하지만 collector의 메서드로 등록된 counting()은 이것과는 같으면서도 다르다.

     

    무슨 이야기냐면 count()나 counting()은 스트림의 요소 갯수를 알려준다는 기능은  동일하다.

     

    하지만 count()가 단순히 전체 스트림 요소의 갯수만을 알려준다면

     

    counting()은 추후 배울 그룹별로 분할된 스트림의 각 요소의 갯수까지 파악이 가능하다.

     

    간단히 설명하면 현재 스트림의 전체 요소는 7개지만, 이걸 남녀따로 스트림 두개로 분할한다고 가정하자.

     

    이럴 경우 counting()을 이용하면 각각 분할된 스트림의 갯수까지 파악이 가능하다.

     

    우선 위의 코드에선 분할을 하지 않았기에 count()나 counting()의 수는 똑같이 7개다.

     

    하지만 추후 분할을 배우면서 실행해보면 다른 결과물을 얻을 수 있다.

     

     

    summingInt(): 모든 요소 더하기

     

     

    summingInt()은 스트림의 요소가 가진 숫자를 모두 다 더해준다.

     

    예를 들어 모든 학생의 총점을 구하고 싶다면 summingInt()을 사용하면 된다.

     

    사실 counting()과 마찬가지로 summingInt()도 sum()이라는 대체 메서드가 있다.

     

    하지만 sum()은 단순히 전체 스트림을 대상으로 숫자를 더하는 반면에

     

    summingInt()의 경우 그룹별 분할된 스트림에서 각각 스트림 별로 숫자를 더할 수 있다.

     

    실제로 사용해보면 sum()보다는 분할된 스트림을 이용하는 경우가 많다.

     

    그래서 보통은 summingInt()의 활용 빈도가 높다.

     

     

    maxBy(),  minBy(): 최대값, 최소값 구하기

     

     

    maxBy(),  minBy()는 스트림의 각 요소 중 최대값과 최소값을 구할 수 있는 메서드다.

     

    maxBy(),  minBy()의 경우 조금 사용 방법이 까다롭다.

     

    각각 매개변수로 Comparator가 가진 comparing()메서드를 전달해야하기 때문이다.

     

     

     

    위 그림에서 볼 수 있듯이 다양한 comparing() 메서드가 존재하고 이 중 하나를 고르면 된다.

     

    위의 코드에선 점수는 int타입이므로 comparingInt()를 선택했다.

     

    그리고 이쯤되면 알겠지만, maxBy(),  minBy()도 분할된 스트림에서 최대값, 최소값을 구할 수 있다.

     

    즉 그룹별로 분할된 스트림 내에서 최대값, 최소값을 구할 수 있다는 것이다.

     

    그리고 maxBy(),  minBy()의 경우 반환타입이 Optional이므로 알아두도록 하자.

     

     

     

    우선 현재 기준으로 maxBy(),  minBy()의 출력 결과다.

     

    가장 높은 점수를 가진 객체와 가장 낮은 점수 가진 객체를 알 수 있다.

     

    스트림의 누적 연산 - reducing()

     

    reducing()은 reduce()와 동일하게 누적 연산 기능을 한다.

     

    하지만 차이점이라면 reduce() 전체 스트림에 대한 누적 연산 기능만을 한다면

     

    reducing()은 분할 스트림에 대한 누적 연산이 가능하다.

     

    일단 사용 방법은 기본적으로 reduce()와 동일하다고 보면 된다.

     

     

     

    아직 스트림을 분할한게 아니다보니 결과물은 당연히 전체 스트림 요소의 합계다.

     

    스트림의 요소 합치기 - joining()

     

    joining()을 사용하면 스트림의 각 요소들을 병합할 수 있다.

     

    위와 같이 스트림에서 이름만 추출한 다음 joining()을 이용해 매개변수로 특정 기호나 문자를 전달한다.

     

    그러면 해당 기호, 문자를 기준으로 데이터를 병합할 수 있다.

     

     

     

    출력 내용을 보면 위와 같이 주어진 기호를 기준으로 모든 데이터가 병합된걸 볼 수 있다.

Designed by Tistory.