[Java] Java8 Stream API (3)

한호성·2023년 3월 2일
0

Java Stream API

목록 보기
3/3

Introduction

실무 개발 중에 Java8 에서 도입 된 Stream 기능을 유용하게 사용하여, Java Collection frameworks의 자료구조를 편하게 사용하였습니다. Stream API를 더 유용하게 사용하고, 자세한 동작원리를 알기 위해, 공부한 글입니다.
[목차]

  • Java Stream API 나오게 된 배경
  • Java Stream API 개념 및 특징
  • Java Stream API 다양한 사용법
  • Java Stream API 사용시 고려해야 할점

요번 글에서는 Stream API의 다음과 같은 것들에 대해 공부해보도록 하겠습니다.

  • 동작순서
  • 성능향상
  • 지연처리

동작순서

스트림이 여러개의 함수로 chain로 이루어져 있을 때, 모든 요소가 중간과정을 하나하나 거친 후 , 다음으로 이어지는 것이 아닌, 한 요소가 모든 파이프라인을 거쳐서 결과를 만들어내고, 다음 요소가 다시 파이프라인을 거쳐 결과를 내는 방식입니다.

다음 코드 예시와 결과를 보면 이해하기 쉽습니다.


        List<String> testList = new ArrayList<>(Arrays.asList("a","b","c"));


        testList.stream()
                .filter(e1 -> {
                    System.out.println("filter() was called");
                    return  e1.contains("a");
                })
                .map(e1->{
                    System.out.println("map() was called");
                    return e1.toUpperCase();
                })
                .forEach(System.out::println);

코드에 대한 결과 값입니다.

첫번째 a 요소를 먼저 실행하고, 파이프라인을 타고 수행 후, 다른 요소들도 순차적으로 파이프라인을 타고 코드가 수행되는 것을 확인 가능합니다.

(이 순서에 대해 인지하고 있는 것이, 성능향상 측면에 있어서 도움이 될 수 있을 것입니다.)

성능향상

위의 수직적으로 파이프라인을 요소마다 돌면서 처리한다는 것을 생각해보면 간단히 성능향상을 시킬 수 있습니다.

두가지 예를 들어보겠습니다.

[1]

        List<String> test= testList.stream()

                .map(
                e1->{
                    
               System.out.println("map() was called");
                    return e1.substring(0);
                })
                .skip(2)
                .collect(Collectors.toList());
               

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

[2]

        List<String> test= testList.stream()
   				.skip(2)
             
                .map(
                e1->{
              System.out.println("map() was called");
                    return e1.substring(0);
                })
              .collect(Collectors.toList());

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

[1] 같은 경우, map을 3번 돌고 난후 ,collect 하기 전에 skip을 하게 되서 skip의 의미가 퇴색됩니다.

[2] 같은 경우 map 돌기전에 skip을 하기 때문에 맵을 한번 돌게 됩니다. 이처럼 vertically 하게 작동하는 것을 알고 있으면, skip, filter, distinct 같은 연산을 적절하게 사용하여 성능향상을 고려할 수 있습니다.

지연 처리 Lazy Invocation

스트림에서 최종 결과는 최종작업이 이루어질 때 계산이 된다. 이게 무슨말인가 하면,다음 코드를 봅시다.

[1]



        List<String> testList =
        new ArrayList<>(Arrays.asList("a","b","c"));

        testList.stream().filter( e1-> {
            counter++;
            return true;
        });
        System.out.println(counter);

[2]

        List<String> testList =
        new ArrayList<>(Arrays.asList("a","b","c"));
        
        testList.stream().filter(e1->{
            counter++;
            return true;
        }).collect(Collectors.toList());
        System.out.println(counter);

[1] 번같은 경우 최종작업 없기 때문에 filter 작업을 수행하지 않아, counter가 0으로 작업이 수행되지 않는 것을 알 수 있고,

[2] 같은 경우에는 최종 작업이 존재하므로 counter가 3인 것을 알 수 있습니다.


Java8 에 도입된Stream API,함수형 프로그래밍 이에 대한 전문가들의 생각

Stream API를 공부하면서, 함수형 프로그래밍의 의미와 사용법에 조금은 알게 되었습니다. Java는 기본적으로 개체지향 프로그래밍 패러다임을 갖고 만들어진 언어로, 이 언어에서, 전부 함수형 프로그래밍으로 패러다임을 바꾸는게 맞는 것인지에 대한 전문가간의 토론이 있었던거 같습니다.

결론부터 먼저 이야기 해드리면, 개발자라면, 언제 함수형 프로그래밍이, 언제 개체지향/절차형 방식을 고수하는 것이 좋은지 직관적으로 알아야 한다고 합니다..

(이런 능력은 코드를 많이 짜보고 피부로 느껴야 하는 것일까.. 좀 어려운 미션입니당..)

  • 뭔지 잘 알고 있다면 개체지향 프로그래밍은 좋다
  • 뭔지 잘 알고 있다면 함수형 프로그래밍은 좋다
  • 뭔지 잘 알고 있따면 함수형 개체지향 프로그래밍은 좋다.

라고 참고한 전문가의 말이 적혀있습니다. 뭔지 잘 아는게 중요한거 같습니다. (잘 알고 있지만, 실천하기 어려운 것들..)

이어서

modern한 스타일을 조심히 써야하는 이유를 성능과 가독성 측면을 통해 정리해보겠습니다.

[1] 성능

성능 관련 컨퍼런스 에서 Stream 의 성능에 대해 정리를 잘 해놓았다.

for-loop 를 전부 Stream.forEach() 로 만들면 성능이 저하된다는 이야기이다. 또한, primitive array는 for-loop를 쓰는 것이 압도적인 성능차이가 존재했다.
(이 글이 작성된지 8년이 지나서.. 지금도 이러한 차이가 있는지는 확인이 안된다.)

성능에 관한 가이드
이 글을 읽어보면 같은 기능을 하는 어떤 것들 중에 성능 측면에서 고려하여 선택할 때, 좋을 것으로 생각됩니다.

[2] 가독성

요즘 실무를 하면서 느끼는 것은, 여러사람이 내 코드 (함수)를 읽고 사용한다는 것이다. 나 또한 다른 사람의 코드를 보고 이해하고 맞는 로직을 작성한다는 것이다. 이 때, 가독성은 중요한 요소 중 하나이며, 개발자라면 꼭 고려해야할 부분이라 생각한다. (유지보수 측면)

복잡한 케이스에서는, 유지보수가 람다를 활용한 함수형 프로그래밍 스타일은 절차형에 비해 어렵다고 합니다.. 실제로 사용 안해봐서, 현재 상태로는, 기존 방식이 저한테 익숙해서 더 편하다고 느껴지는 것인지 알 방법은 없습니다.

결국은, 위의 결론처럼 어느 때에 사용해야하는지 자신만의, 회사만의 약속을 통해 사용하는것이 좋을 것으로 생각됩니다..


마무리

요번 글에서는, Stream API 알고가면 좋은 것들을 정리해 보았습니다. 자세한 내용은, 공식문서나, 아래 출처에 자세히 나와있어서 참고해보시면 좋을거 같습니다.

Reference

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#package.description
https://futurecreator.github.io/2018/08/26/java-8-streams/
https://futurecreator.github.io/2018/08/26/java-8-streams-advanced
https://steady-coding.tistory.com/309
https://mangkyu.tistory.com/112
https://homoefficio.github.io/2016/06/26/for-loop-%EB%A5%BC-Stream-forEach-%EB%A1%9C-%EB%B0%94%EA%BE%B8%EC%A7%80-%EB%A7%90%EC%95%84%EC%95%BC-%ED%95%A0-3%EA%B0%80%EC%A7%80-%EC%9D%B4%EC%9C%A0/

profile
개발자 지망생입니다.

0개의 댓글

관련 채용 정보