[Java] 전역적으로 사용하는 Enum을 효과적으로 활용하기 위한 Stream 구현 방안

Hyo Kyun Lee·2025년 6월 17일
0

Java

목록 보기
97/100

1. 개요

Map에 주어진 모든 내용을 빠르게 순환하고 비교하기위해 Map.EntrySet으로 변환하고 Stream을 통해 구현하였다.

최대한 전역적으로 선언한 Enum을 효과적으로 활용하기 위해 선택한 방법이고, 이에 따라 유지보수나 최적화 관점에서도 유리한 점이 많을 것으로 판단하여 구현하게 되었다.

두서없이 막 구현을 하였는데, 이 참에 Modern Java의 핵심인 Stream을 완전히 이해하고 정복하기 위해 공부한 내용을 정리해둔다.

2. Stream 단계별 구현 과정

일단, Stream을 구현한 형태를 간단히 나타내자면 아래와 같다.

로직의 핵심은 filter 단계에서 매핑되는 정보를 찾아서 추출하는 것인데, 이 부분을 DB화 및 Enum으로 전역적으로 활용하여 클린 코드가 될 수 있도록 구성하였기에 Stream 방식을 활용하게 되었다.

String value = rowMap.entrySet().stream()
    .filter(entry -> { ... })      // 1. 조건에 맞는 entry만 통과
    .map(entry -> { ... })         // 2. entry에서 실제 값을 꺼내서 가공
    .findFirst()                   // 3. 맨 처음 하나만 리턴
    .orElse("");                   // 4. 없으면 빈 문자열

Map에는 아래와 같이 데이터가 들어있고, 지금 처리할 영문명은 "시군구"(engHeader = "SIGUNGU")라고 가정하자.

Map<String, String> rowMap = Map.of(
    "시도", "서울",
    "시군구", "강남구",
    "발전소명", "강남태양광발전소"
);

2-1. peak

entrySet 내용 변경없이(영향을 주지 않고), 내용을 "뽑기만 한다".

.peek(entry -> System.out.println("key : value " + entry.getKey() + ": " + entry.getValue())) //로그 확인용

위의 경우엔 로그 출력을 위해 활용하였다. 개수가 많아 순차적인 디버깅을 하기엔 시간 소요가 많아 peak을 활용하게 되었다.

2-2. filter

조건에 맞는 항목만 "통과"시키며, 다음 단계에서 받는 entry 내용은 필터링하여 통과된 entry 내용만 존재하며, filter는 반드시 true/false의 반환값이 존재해야 한다.

.filter(entry -> {
    String engName = ExcelColumnEnum.getByKorName(entry.getKey());
    return engName != null && engName.equals(engHeader);
})

내부적으로 Map.Entry<String,String>을 돌면서 filter의 구현체인

Stream<T> filter(Predicate<? super T> predicate)

에 따라 entry를 인자로 하고 이에 따른 true/false를 반환하는 Predicate test를 진행한다.

이때 true인 case만 다음 단계로 넘겨주게되고, 위의 경우 "시군구"만 통과한다.

2-3. map

넘겨받은 인자에서 데이터를 실제로 "꺼내서" "가공하는" 작업을 진행한다.

.map(entry -> {
    if (SIGUNGU.equals(ExcelColumnEnum.getByKorName(entry.getKey()))
        || SIDO.equals(ExcelColumnEnum.getByKorName(entry.getKey())))
        return this.getFilteredValue(entry.getValue());
    else
        return entry.getValue();
})

위 과정까지 진행했다면 한쌍의 key-value를 묶은 객체인 Entry만 넘겨져 온다.

이 데이터를 최종적으로 가공해서 다음 단계로 전달한다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map에서 가공한 "형태"는 최초 전달받은 인자의 형태와 일치하지 않아도 되고, 반환한 형태에 따라 mapper를 통해 바뀐 R Type으로 다음 Stream으로 전달된다.

위의 경우엔 "시군구"에 해당하는 데이터인 "강남구"가 전달된다.

2-4. Optional parsing

.findFirst()
.orElse("");

map에서 전달된 형태는 Optional 형태이고, 우리가 알고있는 문자열로 바꾸기 위해선 마지막 파싱작업이 필요하다.

findFirst()를 통해 문자열을 최종 확보하고, 이 문자열이 Null이라면 OrElse를 통해 Default값을 정해줌으로써 최종 value를 받아오게 된다.

3. 참고

Map의 entrySet()은 Map 내부의 (key, value) 쌍을 하나의 객체(Entry)로 묶은 후, 이를 일련의 Set 형태로 반환한다. 즉, 구체적으로 Set<Map.Entry<K,V>> 타입이다.

HashSet, LinkedHashSet은 아니고, 다만 Map 자체가 key값의 유일성을 보장하기 때문에 EntrySet에서도 중복은 일어날 수 없다(Set의 기본 자료구조 특성).

다만 Set과 차이점이라면 EntrySet은 Key, Value모두 가져올 수 있고, Set은 그에 반해 안에 저장된 내용물만 가져올 수 있다는 점이다.

0개의 댓글