[JAVA] Stream을 사용하여 Map의 key와 value들을 추출하기 (Collectors.groupingBy(), Collectors.mapping())
💡 코드가 보이지 않으시다면 드래그 혹은 오른쪽 아래 🌜 아이콘을 눌러 테마 색을 변경해주세요.
안녕하세요!
키크니 개발자 입니다. 🦒
배송추적 관련 API를 연동하면서
List<Object> 에서 Object 중 하나의 필드를 key(String)로 설정하고,
그 외의 필드 중 하나를 value(List<String>)로 삼고 싶었습니다.
상황
@Getter
@AllArgsConstructor
public class Shipping {
private String invoiceNo;
private String status;
}
[
{
"invoiceNo" : "invoiceNo1",
"status" : "배송준비중"
},
{
"invoiceNo" : "invoiceNo2",
"status" : "배송준비중"
},
{
"invoiceNo" : "invoiceNo3",
"status" : "배송준비중"
},
{
"invoiceNo" : "invoiceNo1",
"status" : "배송중"
},
{
"invoiceNo" : "invoiceNo3",
"status" : "배송중"
},
{
"invoiceNo" : "invoiceNo3",
"status" : "배송완료"
},
]
위 처럼 구성 된 데이터에서 invoiceNo(운송장번호)를 기준으로 배송상태값("배송준비중", "배송중", "배송완료")을 뽑아
Map<String, List<String>> 의 형태로 만들고 싶었습니다.
ex : "invoiceNo1" : ["배송준비중", "배송중"], "invoiceNo2" : ["배송준비중"], "invoiceNo3" : ["배송준비중", "배송중", "배송완료"]
이를 for문을 2번 돌려야 하나 고민하고 있었지만,
추후 많은 데이터를 다뤄야할 수 있었기 때문에 for문을 2번 돌리지 않는 것이 좋겠다는 조언을 들은 후
어떻게 해야할지 찾아보았습니다.
일단 먼저 invoiceNo(운송장번호)를 Map의 Key로 묶어줘야했으므로 Collectors.GroupingBy()를 사용했습니다.
Collectors.groupingBy()
- 데이터를 그룹핑해서 Map으로 리턴합니다.
- Thread에는 safe 하지 않습니다.
Lists.newArrayList()
.stream()
.collect(Collectors.groupingBy(o -> o));
Collectors.groupingByConcurrent()
- Thread에 safe 합니다.
Lists.newArrayList()
.stream()
.collect(Collectors.groupingByConcurrent(o -> o));
그 다음으로는 배송상태값(status)을 key에 맞게 value를 묶어줘야하는 작업을 해야했습니다.
해당 값을 추출하기 위해서 Collectors.mapping()을 사용했습니다.
Collectors.mapping()
리턴타입 | method(매개 변수) | 설명 |
Collector<T, ?, R> | mapping(Function<T, U> mapper, Collector<U, A, R> collector> |
T를 U로 매핑한 후, U를 R에 수집합니다. |
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Shipping> shippingList = Arrays.asList(
new Shipping("invoiceNo1", "배송준비중"),
new Shipping("invoiceNo2", "배송준비중"),
new Shipping("invoiceNo3", "배송준비중"),
new Shipping("invoiceNo1", "배송중"),
new Shipping("invoiceNo3", "배송중"),
new Shipping("invoiceNo3", "배송완료")
);
Map<String, List<String>> shippingMap = shippingList.stream()
.collect(Collectors.groupingBy(
Shipping::invoiceNo,
Collectors.mapping(Shipping::status, Collectors.toList()))
);
System.out.print("[invoiceNo1] ");
shippingMap.get("invoiceNo1").forEach(status -> System.out.print(status + ", "));
System.out.print("\n[invoiceNo2] ");
shippingMap.get("invoiceNo2").forEach(status -> System.out.print(status + ", "));
System.out.println();
System.out.print("\n[invoiceNo3] ");
shippingMap.get("invoiceNo3").forEach(status -> System.out.print(status + ", "));
}
}
Shipping(T)를 status(U)로 매핑한 후, List(U)을 Map<String, List<String>> 중 List<String>(R)에 수집하는 것을 의미합니다.
위와 같은 코드를 작성하면 Shipping의 invoiceNo를 기준(key로 삼아)으로 각각의 status를 List으로 담아줄 수 있습니다.
결과 값
[invoiceNo1] 배송준비중, 배송중,
[invoiceNo2] 배송준비중,
[invoiceNo3] 배송준비중, 배송중, 배송완료
Collectors.groupingBy() method는 그룹핑 후,
매핑이나 집계를 할 수 있도록 두 번째 매개값으로 Collector를 가질 수 있습니다.
더 자세한 Collectors.groupingBy()의 method
리턴 타입 | 메소드(매개 변수) | 설명 |
Collector<T, ?, R> | mapping(Function<T, U> mapper, Collector<U, A, R> collector> | T를 U로 매핑한 후, U를 R에 수집 |
Collector<T, ?, Double> | averagingDouble(ToDoubleFunction<T> mapper) | T를 Double로 매핑한 후, Double의 평균값을 산출 |
Collector<T, ?, Long> | counting() | T의 카운팅 수를 산출 |
Collector<CharSequence, ?, String> | joining(CharSequence delimiter) | CharSequence를 구분자로 연결한 String을 산출 |
Collector<T, ?, Optional<T>> | maxBy(Comparator<T> comparator) | Comparator를 이용해서 최대 T를 산출 |
Collector<T, ?, Optional<T>> | minBy(Comparator<T> comparator) | Comparator를 이용해서 최소 T를 산출 |
Collector<T, ?, Integer> | summingInt(ToIntFunction) summingLong(ToLongFunction) summingDouble(ToDoubleFunction) |
Int, Long, Double 타입의 합계 산출 |
⭐️ 참고한 곳
https://akageun.github.io/2019/08/06/java-stream-groupby.html
https://steady-coding.tistory.com/318
배워야 할 것이 더 많은 주니어 개발자입니다. 🐣
내용 전달보다는 정리를 목적으로 포스팅을 하고 있습니다.
잘못 된 내용이나 부족한 부분은 댓글로 주시면 감사드리겠습니다.