실무에서 사용하기 좋은 Stream Collector 기능
TL; DR
Stream에서 사용할수 있는 Collect 함수를 알아보자..
본론 ㄱ
뭘 할까 고민하다가 List처럼 Stream을 붙였을때 Map을 만든다던가 처럼 사용하기 유용한 Collectors 함수를 알아보는 시간을 가지겠다.
기본적으로 사용하게될 DTO
class Dto {
private Long id;
private String name;
private Integer value;
public Dto(Long id, String name, Integer value) {
this.id = id;
this.name = name;
this.value = value;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Integer getValue() {
return value;
}
@Override
public String toString() {
return "Dto{" +
"id=" + id +
", name='" + name + '\'' +
", value=" + value +
'}';
}
}
이런 객체가 있다고 가정해보자
그리고 매번 만들기는 귀찮으니 Random으로 객체를 만들어주는 함수를 만들어보겠다
class RandomCollectors {
private static final List<String> names = List.of("서울", "대구", "부산", "울산", "춘천", "인천", "대전");
static List<Dto> makingRandomList(int size) {
List<Dto> dtos = new ArrayList<>();
for (long i = 0; i <size; i++) {
int idx = ThreadLocalRandom.current().nextInt(0, names.size());
dtos.add(new Dto(i, names.get(idx), ThreadLocalRandom.current().nextInt(100)));
} return dtos;
}
}
1. Collectors.toMap()
해당 기능은 일단 기본적으로 Map객체를 만든다. 맵 객체를 만들어서 ID값과 같은 객체를 찾아 Mapping하고 싶을때 사용한다.
Map<Long, Dto> collect = list.stream().collect(Collectors.toMap(Dto::getId, it -> it));
//Map의 value값을 DTO로 쓰고 싶다는건데.. it->it은 뭔가 짜친다. (물론 무슨 뜻인지는 이해하겠지만...) 좀 더 명확하게 하고 싶다면?
Map<Long, Dto> collect = list.stream().collect(Collectors.toMap(Dto::getId, Functions.identity()));
//이렇게 Functions.identity를 사용하자
이러면 다음과 같이 나온다.
@Test
@DisplayName("collectors.toMap을 잘써보자")
void test() {
List<Dto> list = List.of(new Dto(1L ,"이름", 1), new Dto(2L, "이름2", 2));
Map<Long, Dto> collect = list.stream().collect(Collectors.toMap(Dto::getId, it -> it));
assertThat(collect.size(), is(2));
log.info("map get 1L : {}", collect.get(1L));
}
//응답.
map get 1L : Dto{id=1, name='이름', value=1}
언제 사용하지?
개인적으로는 DTO 생성시 → 여러가지 값을 조회해서 넣어야하는 경우 Index Key를 기반으로 찾아서 DTO에 조회해야하는 경우 편한다.
물론 JPA 경우 JOIN Table을 통해서 Response자체에서 값을 다 묶어서 주는 방법도 있겠지만, 꼭 그런 값만 있는게 아니니까 ㅎㅎ; 외부 APi를 통해서 들어오는 경우도 왕왕 있었는데 그런 경우 좋았다
2. Collectors.groupingBy
@Test
@DisplayName("collectors.grouping by을 잘써보자")
void test1() {
Map<String, List<Dto>> group = RandomCollectors.makingRandomList(20).stream().collect(Collectors.groupingBy(Dto::getName));
log.info("map: {}", group);
}
//이러면 다음처럼 grouping이 가능해진다.
map: {
울산=[Dto{id=1, name='울산', value=34}, Dto{id=3, name='울산', value=87}, Dto{id=5, name='울산', value=24}, Dto{id=18, name='울산', value=99}],
대전=[Dto{id=7, name='대전', value=36}, Dto{id=8, name='대전', value=16}, Dto{id=12, name='대전', value=78}, Dto{id=15, name='대전', value=40}],
서울=[Dto{id=6, name='서울', value=54}, Dto{id=19, name='서울', value=7}],
부산=[Dto{id=16, name='부산', value=73}],
대구=[Dto{id=11, name='대구', value=7}],
인천=[Dto{id=2, name='인천', value=80}, Dto{id=4, name='인천', value=68}, Dto{id=9, name='인천', value=28}, Dto{id=10, name='인천', value=96}, Dto{id=13, name='인천', value=22}, Dto{id=14, name='인천', value=26}, Dto{id=17, name='인천', value=47}],
춘천=[Dto{id=0, name='춘천', value=31}]
}
언제쓰지?
복잡한 집계를 만들고 싶을때 괜찮다. SQL 만능으로 집계하느냐.. 아니면 Application 단으로 한칸 올려서 집계할지에 대한 트레이드 오프는 개발자가 고민해야하는 문제이지만, 해당하는 트레이드 오프를 기반으로 고민해보면 된다.
혹은 묶어서 필터링하고 싶을때도 유용했다. 필터링은 Stream의 필터를 써도 되지만, 해당하는 Grouping 한 값들중에 전부다를 필터링하지 않는 경우도 좋았다.
3. partitioningBy
@Test
@DisplayName("collectors.partitioningBy 을 잘써보자")
void test3() {
Map<Boolean, List<Dto>> partitioningBy = RandomCollectors.makingRandomList(20).stream()
.collect(Collectors.partitioningBy(it -> it.getId() < 10));
assertThat(partitioningBy.get(true), hasSize(10));
log.info("map true: {}", partitioningBy.get(true));
log.info("map false: {}", partitioningBy.get(false));
}
/// 결과
map true: [Dto{id=0, name='춘천', value=90}, Dto{id=1, name='대전', value=22}, Dto{id=2, name='대전', value=56}, Dto{id=3, name='대구', value=1}, Dto{id=4, name='울산', value=85}, Dto{id=5, name='울산', value=76}, Dto{id=6, name='인천', value=67}, Dto{id=7, name='울산', value=60}, Dto{id=8, name='울산', value=91}, Dto{id=9, name='대구', value=82}] //9까지만 있음...
map false: [Dto{id=10, name='울산', value=47}, Dto{id=11, name='부산', value=35}, Dto{id=12, name='울산', value=64}, Dto{id=13, name='서울', value=8}, Dto{id=14, name='춘천', value=82}, Dto{id=15, name='서울', value=38}, Dto{id=16, name='부산', value=14}, Dto{id=17, name='대전', value=49}, Dto{id=18, name='대전', value=39}, Dto{id=19, name='대전', value=63}] // 그외 나머지..
언제 쓰지?
GroupingBy보다는 단순하게 조건을 가져갈때 유용하다 Api Response안에 Detail응답처럼, Response -detail이 있으면 true에 detaildl 없을때는 false로 넣어서 사용하는 식으로 처리해보면 꽤 유용하다. detail이 있으면, 해당하는 값을 true로 처리해서 만들면 된다.
결론
Stream을 사용할 일이 꽤 많은데 다음과 같은 예제로 좀 더 사용해 보면, Application의 여러 가지 정보를 조회해서 뭉칠 수 있다는 점이 매력적이다. 잘 사용하여 효율적인 서비스를 만들어 보자!
'Java' 카테고리의 다른 글
Slack App Directory를 활용하여 Webhook보다 퀄 좋은 메세지 발송하기 (0) | 2024.05.12 |
---|---|
클린 코드 - 1. 객체 지향 생활 체조 (0) | 2024.03.31 |
Stream을 Null Safety하게 사용하기 (0) | 2024.02.17 |
Deprecated 잘쓰는 법 (0) | 2023.09.02 |
JWT Expired 시간이 토큰 발급시간보다 이전 시간인 경우 (2) | 2022.08.18 |