이전 포스팅에서 Stream API의 최종 연산(Terminal Method)에 쓰이는 Collect 를 간단하게 다뤄봤습니다. Collect는 Collector 타입의 매개변수를 전달받아 원하는 자료구조로 변환시켜주는 기능이 대표적인데요. 이 기능 이외에도 Join 연산, 수학 연산 등의 기능을 제공합니다. 이번 시간에는 Collect를 잘 활용할 수 있도록 기능들에 대해서 배워보고 실습해보는 시간을 가져보겠습니다.☺️
Stream.collect() Method는 Java8 의 Stream API의 최종 연산(terminal method) 중 하나 입니다. collect() 메서드는 스트림의 데이터 요소들을 특정 자료구조로 변환하거나, 특정 논리를 적용시킨 결과를 반환해주는데요, 이 collect() 메서드의 핵심은 Collector 인터페이스 구현을 통해 제공되어집니다.
import java.util.stream.Collectors;
Collectors
인터페이스는 CollectorImpl
내부 클래스 라는 구현체를 가지고 있습니다. Stream API 의 collect() 의 toList(), toSet() 메서드는 ColletorImpl
객체를 생성하여 반환하는 형식으로 구현되어져있는데요.
먼저 자료구조 변환 기능에 해당하는 연산부터 알아보는 시간을 가져보겠습니다.
Collectors.toList() 메서드는 스트림의 요소들을 모아 List 인스턴스를 반환해주는데요.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
List<String> sportList = Arrays.stream(sports).collect(Collectors.toList());
System.out.println(sportList.toString());
// Result
// [soccer, football, baseball, basketball]
추가로, Java 10 버전에서 생긴 기능인 toUnmodifiableList() 메서드는 반환된 List 객체를 수정불가능하게끔 만들어줍니다. 만약 수정을 할 시, UnsupportedOperationException 예외를 발생시키는 것을 확인할 수 있습니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
//List<String> sportList = Arrays.stream(sports).collect(Collectors.toList());
List<String> sportList = Arrays.stream(sports).collect(Collectors.toUnmodifiableList());
sportList.add("tennis");
System.out.println(sportList.toString());
결과
Collectors.toSet() 메서드는 스트림의 요소들을 모아 Set 인스턴스를 반환해주는데요.
예제 코드
String[] sports = {"soccer","football","football","basketball"};
Set<String> sportSet = Arrays.stream(sports).collect(Collectors.toSet());
System.out.println(sportSet.toString());
결과
[soccer, basketball, football]
Set 자료형은 요소의 중복을 제거하기 때문에, 중복이 제거된 형태의 출력값을 확인할 수 있었습니다.
마찬가지로, Java 10 버전에서 수정불가능한 Set 자료형을 만들어주는 toUnmodifiableSet() 메서드의 기능을 제공하였는데요. 만약 수정을 할 시, UnsupportedOperationException 예외를 발생시키는 것을 확인할 수 있습니다.
예제 코드
String[] sports = {"soccer","football","football","basketball"};
Set<String> sportSet = Arrays.stream(sports).collect(Collectors.toUnmodifiableSet());
sportSet.add("basketball");
System.out.println(sportSet.toString());
결과
위에서 살펴본 toList(), toSet()은 특정 Collection(ArrayList, LinkedList, HashSet 등)으로 변환이 제한되어져 있었는데요. 그래서 만약 특정 Collection으로 만들어주어야 할때는 Collectors.toCollection() 메서드를 사용하면 가능합니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
List<String> sportList = Arrays.stream(sports).collect(Collectors.toCollection(ArrayList::new));
System.out.println(sportList.toString());
System.out.println(sportList.getClass());
결과
[soccer, football, baseball, basketball]
class java.util.ArrayList
Collectors.toMap() 메서드는 스트림의 요소들을 모아 Map 인스턴스를 반환해주는데요. toMap() 메서드는 keyMapper, valueMaaper 를 매개변수로 사용합니다. → toMap(keyMaaper, valueMapper)
예제로는 sports의 요소들이 key 값을 갖고, 요소의 길이를 value로 갖게 하는 Map 객체를 만들게끔 구현하였습니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
Map<String,Integer> sportMap = Arrays.stream(sports).collect(Collectors.toMap(String::toString, String::length));
System.out.println(sportMap.toString());
System.out.println(sportMap.getClass());
결과
{soccer=6, basketball=10, baseball=8, football=8}
class java.util.HashMap
만약 키가 중복 되면 어떻게 될까요?
예제 코드
String[] sports = {"soccer","football","football","basketball"};
Map<String,Integer> sportMap = Arrays.stream(sports).collect(Collectors.toMap(String::toString, String::length));
System.out.println(sportMap.toString());
System.out.println(sportMap.getClass());
결과
"football"이라는 key가 중복되어 IllegalStateException 예외를 발생시키는 것을 확인할 수 있습니다. 이럴 땐 다음과 같이 중복에 대한 처리를 할 수 있습니다.
예제 코드
String[] sports = {"soccer","football","football","basketball"};
Map<String,Integer> sportMap = Arrays.stream(sports).collect(Collectors.toMap(String::toString, String::length,(post, pre) -> post));
System.out.println(sportMap.toString());
System.out.println(sportMap.getClass());
결과
{soccer=6, basketball=10, football=8}
class java.util.HashMap
toMap() 메서드의 세번째 인자(mergeFunction)에 (post, pre) -> post)
함수를 전달시켜줌으로써 동일한 키가 들어올 경우 기존의 키를 선택하는 방식으로 중복 처리를 진행하였습니다.
Collectors - toMap 메서드
Collectors.collectingAndThen() 메서드는 Collecting 을 진행한 결과를 바탕으로 메서드를 추가로 진행할 수 있게 해주는 메서드입니다.
예제에선 만들어진 리스트의 toString() 메서드를 호출하게 구현하였습니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
System.out.println((String) Arrays.stream(sports).collect(Collectors.collectingAndThen(Collectors.toList(),Collection::toString)));
결과
[soccer, football, baseball, basketball]
collect() 메서드는 자료구조로 변환시켜주는 기능 이외에 Stream의 요소들을 특정 조건으로 join, grouping, partitioning 을 수행할 수 있는 기능을 제공하는데요. 이 3가지 기능을 중점적으로 공부하고 실습해보는 시간을 가져보도록 하겠습니다.
joining 메서드는 List 가 아닌 String으로 반환시켜주는 기능을 수행합니다.
delimiter(구분자) 를 매개변수로 전달하여 join 연산을 수행할 수 있습니다.
예제코드
String[] sports = {"soccer","football","baseball","basketball"};
String sportStr = Arrays.stream(sports).collect(Collectors.joining(" + "));
System.out.println(sportStr);
결과
soccer + football + baseball + basketball
Collectors.groupingBy()은 데이터베이스의 테이블 조작 연산 중 하나인 groupby 연산을 제공해줍니다.
groupingBy의 매개변수는 classifier, downStream 으로 이루어져 있는데
classifier에 그룹핑할 기준을, downStream을 그룹핑 결과를 어떤 자료구조로 반환시킬까에 대한 결정을 할 메소드를 전달할 수 있습니다.
예제에선 스포츠의 문자열 길이를 기준으로 그룹핑한 결과를 List로 반환하게끔 구현하였습니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
Map<Integer, List<String>> sportGroupMap = Arrays.stream(sports).collect(Collectors.groupingBy(String::length,Collectors.toList()));
System.out.println(sportGroupMap);
결과
{6=[soccer], 8=[football, baseball], 10=[basketball]}
Collectors.partitioningBy()은 Collectors.groupingBy()를 응용하여 Predicate 함수형 인터페이스의 반환값이 true인 집합군과 false인 집합군을 Map 형태로 반환해줍니다.
예제에선, 스포츠의 문자열 길이가 6이하인 경우 true를 반환하는 Predicate 함수형 인터페이스 구현체를 전달해보았습니다.
예제 코드
String[] sports = {"soccer","football","baseball","basketball"};
Map<Boolean, List<String>> sportPartitionMap = Arrays.stream(sports).collect(Collectors.partitioningBy(s->s.length()<=6));
System.out.println(sportPartitionMap);
결과
{false=[football, baseball, basketball], true=[soccer]}