[Java]Stream 개념, 성능, 사용 예제(feat. for문 비교)

JANG SEONG SU·2023년 7월 31일
1

Java

목록 보기
10/10
post-thumbnail

1. Stream 이란?

JDK 1.8(8버전)부터 제공된 컬렉션 혹은 배열에 저장된 요소를 하나씩 참조하여 람다 표현식으로 처리할 수 있는 반복자이다. 스트림이 존재하기 이전에는 Iterator 인터페이스를 사용했다고 한다.

Iterator 를 사용한 반복 처리

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator iterator = numbers.iterator();

while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

Stream을 사용한 반복 처리

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = numbers.stream();

stream.forEach(number -> System.out.println(number));

2. Stream 사용 예제(코드)

Array 스트림

int[] arr = {1,2,3,4,5};

Arrays.stream(arr).forEach(n -> System.out.print(n));	//12345
int sum = Arrays.stream(arr).sum();		//sum = 15

ArrayList 스트림

List<Customer> cList = new ArrayList<>();
cList.add(new Customer("Lee", 10));		//name, age
cList.add(new Customer("Kim", 15));
cList.add(new Customer("Hong", 13));

System.out.println(cList.stream().mapToInt(c -> c.getAge()).sum());     //mapToInt 대신 map을쓰면 Integer(Wrapper)타입으로 반환하기 때문에 sum()작동 안됨

cList.stream().filter(c->c.getAge()>=12).filter(c->c.getAge()>=13).map(c->c.getName()).sorted().forEach(s->System.out.println(s));    //filter,map,sort,forEach 순서갖 중요
        

reduce()

Stream의 요소들을 하나의 데이터로 만든다.

String[] greetings = {"안녕하세요~", "hello", "Good morning"};

System.out.println(Arrays.stream(greetings).reduce("", (s1,s2)->{
    if(s1.length() >= s2.length()) return s1;
    else return s2;
}));		//Good morning 출력

🔼동작 과정

  • "안녕하세요~", "hello" 비교 ➡ "안녕하세요~"
  • "안녕하세요~", "Good morning" 비교 ➡ "Good morning"
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));	//55출력

🔼동작 과정

  • total(0) + n(1) = 1
  • total(1) + n(2) = 3
  • total(3) + n(3) = 6
  • total(6) + n(4) = 10
  • total(10) + n(5) = 15
  • total(15) + n(6) = 21
  • total(21) + n(7) = 28
  • total(28) + n(8) = 36
  • total(36) + n(9) = 45
  • total(45) + n(10) = 55

3. Stream 특징 및 주의점

  • 람다 표현식
    스트림은 람다식으로 요소 처리 코드를 제공한다. 스트림이 제공하는 대부분의 요소 처리 메소드는 함수형 인터페이스를 사용하므로, 람다식으로 요소 처리 코드를 제공할 수 있다.
    (람다식은 함수형 객체로 final로 선언된 변수만 읽을 수 있기 때문에, 본 객체(데이터)에 어떤 영향도 끼치지 않는다.)

  • 생성, 중간처리, 최종처리
    후술하겠지만, 스트림의 처리는 생성, 중간처리, 최종처리 3단계로 구분된다.

  • 재사용 불가능
    스트림은 생성되고, 중간처리를 거쳐 최종처리까지 완료되면 닫히게된다. 이미 닫힌 스트림은 재사용할 수 없으며, 재사용을 시도할 경우 예외가 발생한다. 즉 스트림은 일회용이다.

  • continue, break 불가능
    스트림은 continue, break가 불가능하므로, 중간에 break를 사용하는 조건문이 있다면, 스트림 대신 for문을 사용하는 것이 좋다. 예를 들어, 10번의 루프를 돌 때, for문에서는 5번만 반복 후 break하는 조건문을 만들 수 있지만, 스트림에서는 10번 모두 돌아야 하므로 오버헤드가 발생할 수 있다.


4. Stream VS For-loop

  • primitive type(원시 타입)에 대한 연산만 이루어지는 경우, for문이 Stream보다 몇 배 이상 빠르다.
    • for문은 Java 1부터 만들어진 것으로, 내부 최적화가 잘 되어 있는 반면, Stream은 Java 8 이후로 등장한 것으로, for문에 비해 내부 최적화가 덜 되어있다.
    • Stream을 사용하는 과정에서, Stream 객체 생성, Stream에서 필요한 여러 다른 객체를 생성하는 과정에서 JVM이 여러 작업을 수행한다.
    • 배열의 경우 Index를 통해 메모리에 직접 접근하므로, for문이 더 빠르다.
    • 반면에 Stream의 경우 Wrapper(Integer)타입이 들어오므로, 이것을 박싱/언박싱 하는 과정에서 상당한 오버헤드가 발생하여, 더 느리다.
  • 하지만 List에서는 성능 차이가 미미하다. 그 이유는 앞서 말한 박싱/언박싱에 대한 오버헤드가 지배적으로 컸지만, List에서는 그럴 필요가 없기 때문이다. 또한 List는 for 문에서도 Stream과 마찬가지로 간접 참조를 해야하므로, 성능 상에 큰 차이는 없다.

primitive type(int)형, 그리고 데이터에 대한 접근만 한다 했을 경우, for문 보다 Stream이 두배 가량 빠르다
하지만 Wrapper(Integer)과 같은 데이터를 놓고 본다면 for문과 Stream의 성능 차이는 거의 없다.
즉, primitive type(int)형은 JVM에서 Stack영역에 저장되어 직접 참조로 속도가 빠르지만, Wrapper(Integer)과 같은 데이터는 heap영역에 저장되기에 간접 참조가 일어나 속도가 다른것 입니다.

참고 : 스트림은 언제나 좋을까?

profile
Software Developer Lv.0

1개의 댓글

comment-user-thumbnail
2023년 7월 31일

좋은 정보 얻어갑니다, 감사합니다.

답글 달기