관리 메뉴

개발그래머

'filter()' and 'map()' can be swapped 본문

Java

'filter()' and 'map()' can be swapped

임요환 2023. 7. 31. 21:31

발견

  • 코드를 리팩터링하며 보인 문구이며 왜 이 문구가 발생했는지 궁금하여 찾아보게되었다.

과정

List<String> emails = communityUserRepository.findByCommunityId(1L).stream()
                    .filter(communityUser -> communityUser.getCreatedUser().getEmail() != null)
                    .map(communityUser -> communityUser.getCreatedUser().getEmail()).collect(Collectors.toList());
  • 해당 커뮤니티에 해당하는 유저들을 가져오는 로직에서 'filter()' and 'map()' can be swapped 라는 문구가 인텔리제이에서 발생한것이다
List<String> emails = communityUserRepository.findByCommunityId(1L).stream()
                    .map(communityUser -> communityUser.getCreatedUser().getEmail())
                    .filter(email -> email != null).collect(Collectors.toList());
  • 인텔리제이가 권장하는 방법으로 수정하면 위와 같은 로직이 나오게된다.
  • 먼 차이가 있길래 인텔리제이에서는 위와 같은 방법을 권장하는지 궁금하게 되어 검색을 하게 되었다.

해결

`communityUser.getCreatedUser().getEmail()` is invoked twice - once inside the filter and it is also the mapping function, so instead of doing this twice, it would be better to first map it using `getCreatedUser().getEmail()` and then filter it out


`communityUser.getCreatedUser().getEmail()`은 두 번 호출됩니다. 한 번은 필터 내부에 있고 매핑 함수이기도 하므로 이 작업을 두 번 수행하는 대신 먼저 `getCreatedUser().getEmail( )`을 사용하여 매핑한 다음 필터링하는 것이 좋습니다.

https://stackoverflow.com/questions/66979712/filter-and-map-can-be-swapped

  • 위와 같은 문구가 올라와 있는 스택오버플로어 글을 발견하였고 객체의 메서드를 여러번 호출하는 것보다는 미리 한번만 선언하여 매핑하고 나머지 후에 로직을 처리하는 것이 성능면에서 유리하다고 나와있었다.
  • 물론 엄청나게 많은 데이터가 아니라 두개의 순서가 바뀐다고 해서 크게 영향을 받을 것같진 않았지만 궁금해서 찾아보게 되었다.

검증

public class FilterTest {
    List<User> users = new ArrayList<>();
    @BeforeEach
    void setUp() {
        for (int i = 0; i < 2000000; i++) {
             if(i % 3 == 0) {
                 users.add(new User("요환"+i, null));
             } else {
                 users.add(new User("요환"+i, "dyghks7102@naver.com"));
             }
        }
    }

    @Test
    void filterMapTest() {
        long beforeTime = System.currentTimeMillis();

        List<String> emails = users.stream()
                .filter(user -> user.getEmail() != null)
                .map(user -> user.getEmail())
                .collect(Collectors.toList());

        System.out.println(emails.size());

        long afterTime = System.currentTimeMillis();
        long diffTime = afterTime - beforeTime;
        System.out.println("실행 시간(ms): " + diffTime);
    }

    @Test
    void mapFilterTest() {
        long beforeTime = System.currentTimeMillis();

        List<String> emails = users.stream()
                .map(user -> user.getEmail())
                .filter(email -> email != null)
                .collect(Collectors.toList());

        System.out.println(emails.size());

        long afterTime = System.currentTimeMillis();
        long diffTime = afterTime - beforeTime;
        System.out.println("실행 시간(ms): " + diffTime);
    }

    class User {
        String name;
        String email;

        public User(String name, String email) {
            this.name = name;
            this.email = email;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }
    }
}
  • 간략하게 테스트 코드를 짜서 성능차이가 얼마나 있나 검증을 해보았다.
  • 몇천건 정도에는 의미있는 차이가 없었지만 백만건이 넘어가는 부분에서는 의미있는 차이를 보여주기 시작했다.
  • 평균적으로 map을 하고 난 후 filter를 하는게 10ms정도 더 빠르게 종료되었다.
  • 성능이 중요하면 메서드의 호출을 얼마나 하나도 신경써야하는 부분 중 하나일 것 같다.