devnoong.log
728x90

Stream에 많이 사용되는 Collectors에 대해 알아보는 시간을 가져보도록 하겠습니다.

 

 

1. Collectors 인터페이스란?

Collector 클래스는자바스트림 API에서 제공하는 기능 중 하나로,  Stream에서 수행한 연산 결과를 수집하여 다양한 형태의 컬렉션을 반환할 수 있는 정적 메소드를 제공하는 클래스이다.

 

Collectors를 사용하면 요소들을 적절하게 그룹화,분할,집계,변환 할 수 있지만, 이를 위해서는 추가적인 객체 생성이 필요하다. 따라서 트림의 크기가 작은경우나, 간단한 작업을 처리할때는 Collectors 를 사용하지 않고 직접 구현하는것이 더 효율적이다. 

 

하지만, 스트림의 크기가 크고 복잡한 작업을 수행할 때는 Collectors를 사용하면 코드의 가독성과 유지보수성이 향상될 수 있다. 또한, Collectors는 병렬처리를 지원하므로 대용량 데이터 처리에 적합하다.

 

따라서 Collectors의 사용 여부는 데이터의 크기와 작업의 복잡성,성능 등을 고려하여 결정해야 된다.

 

2. Collectors 메소드 사용법

 

주로 자주 사용하는 Collectors 클래스 메소드는 아래와 같다.

 

  • toList() : Stream을 List로 변환한다.
  • toSet() : Stream을 Set으로 변환한다.
  • toMap() : Stream을 Map으로 변환한다.
  • joining() : Stream의 문자열 요소를 결합하여 하나의 문자열로 반환한다.
  • groupingBy() : Stream의 요소를 그룹화하여 Map으로 반환한다.
  • partitioningBy() : Stream의 요소를 분할하여 Map으로 반환한다.

 

2-1. Collectors.toList

toList()

 

매개변수가 존재하지않고, 스트림에서 처리된 요소들을 List 형태로 반환해준다

 

 

 

아래는 사용 예제를 나타냅니다.

 

String[] words = {"apple", "banana", "cherry", "date", "elderberry", "fig"};
List<String> longWords = Arrays.stream(words)
    .filter(w -> w.length() >= 5)
    .collect(Collectors.toList());

System.out.println(longWords); // 출력 결과: [apple, banana, cherry, elderberry]

 

2-2. Collectors.toSet

toSet()

 

매개변수가 존재하지않고, 스트림의 요소를 Set으로 수집하여 중복을 제거하고 유일한 값들로만 구성된 컬렉션을 반환한다.

 

 

아래는 사용 예제를  나타냅니다.

 

import java.util.*;
import java.util.stream.*;

List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5, 5);
Set<Integer> uniqueNumbers = numbers.stream()
                                    .collect(Collectors.toSet());

System.out.println(uniqueNumbers);
// 출력 결과: [1, 2, 3, 4, 5]

 

2-3. Collectors.toMap

 

toMap(Function<? super T, ? extends K> keyMapperFunction<? super T, ? extends U> valueMapper)

 

toMap 메소드는 위의 형식으로 선언되어 있으며, 매핑함수와 값 추출 함수를 인수로 받는다.

원하는 형태로 key,value를 설정해서 Map을 반환 할 수 있다.

 

매핑 함수는 스트림 요소를 key-value 형태로 변환하고, 값 추출 함수는 해당 key-value에서 값을 추출한다.

 

Collectors.toMap은 다양한 오버로드 버전을 가지고 있으며, 기본적으로 키 중복을 허용하지 않는다.

하지만 키가 중복되는 경우 기존값을 유지할것인지 , 새로운 값을 사용 할 것인지 결정하는 병합 함수도 지정할 수 있다.

 

 

아래는 사용 예제를  나타냅니다.

 

List<String> list = Arrays.asList("a", "b", "c");
Map<String, Integer> map = list.stream().collect(Collectors.toMap(Function.identity(), String::length));
System.out.println(map);

 

Function.identiy 함수를 통해 스트림 요소를 키로 매핑해서 사용할 수 있다.

메소드 참조를 사용하여 문자열의 길이를 value값으로 지정할 수 있다.

 

 

만약, 스트림요소를 Map으로 변환 할 때 동일한 키를 가진 요소가 있으면 illegalStateException 이 발생한다.

 

 

이러한 키 충돌을 방지하기 위해서 toMap 메소드에 두개의 매개변수 인자를 추가해줘야 된다.

 

첫번째 매개변수는 중복된 키가 발생한 경우 무엇을 사용할지 지정하는 함수가 선언되야 한다. (필수)
두번째 매개 변수는 Map의 구현체를 선택하는 함수이다. 생략이 가능하며 생략시에 HashMap의 형태로 반환된다. (생략가능)
Map<String, Integer> map = list.stream().collect(Collectors.toMap(Function.identity(), String::length, (v1, v2) -> v1, LinkedHashMap::new));

 

위의 코드에서 세번째 매개변수는 람다식을 이용하여 중복된 키가 발견되었을때 이전값을 유지하도록 구현되었다.

네번째 매개변수로 LinkedHashMap을 선언하여 새로운 요소가 추가될 때마다 새로운 LinkedHshMap 객체를 만든다.

생략시에는 기본 설정인 HashMap으로 반환된다.

 

결론은 키가 중복된 상황을 대비해서 toMap(keyMapper,valueMapper,mergeFuntion) 의 형식으로 사용하는 것을 권장한다.

 

 

2-4. Collectors.joining

 

joining()
joining(CharSequence delimiter)
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

 

 

첫번째 메소드인 joining() 은 구분자없이 요소를 연결하여 하나의 문자열로 반환한다.

두번재 메소드인 joining(delimiter) 는 구분자를 인자로 받아 구분자로 요소들을 연결하여 하나의 문자열로 반환한다.

세번째 메소드인 joining(delimiter,prefix,suffix) 는 구분자뿐만 아니라 접두사와 접미사를 인자로 받아 구분자로 연결 후 접두사와 접미사를 추가한다.

 

 

아래는 사용 예제를 나타냅니다.

 

List<String> strings = Arrays.asList("Java", "is", "awesome");
String result = strings.stream().collect(Collectors.joining(", "));
System.out.println(result); // "Java, is, awesome"
result = strings.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(result); // "[Java, is, awesome]"

 

 

 

2-5. Collectors.groupingBy

 

groupingBy(Function<? super T, ? extends K> classifier)

groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

첫번째 groupingBy의 경우 classifier함수에 의해 그룹화하여 Map<K,List<T>> 로 반환한다.
List의 형태로 반환되는 것이 특징이다.
두번째 groupingBy의 경우 classifie 함수에 의해 그룹화 되고, 룹화된 요소들에 대해 collector형태를 지정해 VALUE값을 지정할 수 있다.

 

 

아래는 사용예제를 나타냅니다

List<Student> students = Arrays.asList(
    new Student("John", 2),
    new Student("Jane", 3),
    new Student("Mike", 1),
    new Student("Emily", 2),
    new Student("Tom", 3)
);

Map<Integer, List<Student>> studentsByGrade = students.stream()
    .collect(Collectors.groupingBy(Student::getGrade));
    
    //{1=[Mike], 2=[John, Emily], 3=[Jane, Tom]}

Student에서 제공하는 getGrade함수를 사용해 성적값을 기준으로 grouping 하여 List형태로 value값을 반환해준다.

 

Map<Integer, Set<String>> studentsByGrade = students.stream()
    .collect(Collectors.groupingBy(
        Student::getGrade,
        Collectors.mapping(Student::getName, Collectors.toSet())
    ));
    //{1=[Mike], 2=[John, Emily], 3=[Jane, Tom]}

List형태가 아닌 다른 형태로 반환하기 위해서 Collecotrs.mapping 메소드를 이용해 Set<String> 형태로 value값을 설정해준다.

 

Map<Integer, String> studentsByGrade = students.stream()
    .collect(Collectors.groupingBy(
        Student::getGrade,
        Collectors.mapping(Student::getName, Collectors.joining(", "))
    ));
    //{1=Mike, 2=John, Emily, 3=Jane, Tom}

Set타입이 아닌 문자열로 value값을 지정하기 위해 Collector.joining을 이용하여 모든 문자열을 하나로 합쳐준다.

 

 

 

2-6. Collectors.partitionBy

 

partitionBy(Predicate<T> predicate)

partitionBy(Predicate<T> predicate, Collector<? super T, A, D> downstream)

 

첫번째 partitionBy는 Predicate 객체(Bolean 값을 반환해주는 함수)를 인자로 받아, 스트림의 각 요소를 평가하여 True 그룹과 False 그룹으로 할당해준다. Map<Boolean,List<T>> 타입의 결과를 반환한다.
두번째 partitionBy는 Predicate객체와 dowstream 매개변수를 통해 Map value값의 타입을 지정할 수 있다.

 

 

아래는 사용 예제 코드를 나타냅니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<Boolean, List<Integer>> evenOddMap = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

System.out.println(evenOddMap); //{false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}

predicate객체만 인자로 설정하여 List 타입으로 반환되었다.

 

Map<Boolean, Long> evenOddCountMap = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));

System.out.println(evenOddCountMap); //{false=5, true=5}

dowstream 매개변수로 Collectors.counting을 사용하여 Long타입으로 반환 되었다.

 

 

 

 

 

728x90