카테고리 없음

[JAVA] Stream과 List는 무슨 차이가 있을 까?

koldin 2022. 10. 11. 05:54

김영한 님의 강의를 보다가 stream()을 쓰고 난 후에 foreach나 map과 같은 함수를 쓰는 것을 보았습니다.

조금 이상했죠, List타입도 Collection을 상속받으니까 foreach나 map을 쓸 수 있을 텐데 왜 굳이 stream을 한번 더 끼는 거지? 약간 이해가 잘 안 갔습니다. 뭐 제가 아직 잘 몰랐으니까 그럴 수 있죠.

그래서 stream을 했을 때와 안 했을 때의 성능 비교 및 List(이하 컬랙션)와 stream의 차이점을 알아볼 겁니다.

 

먼저 차이점 입니다.

스트림과 리스트의 차이점을 정리하자면

스트림은

    - 원본 데이터의 수정이 없음

    - 데이터 처리를 주목적으로 하는 인터페이스를 제공함

    - 저장공간을 독립적으로 갖지 않음

    - 거의 무한함

    - 간결함

    - Lazy 베이스 구현

    - 일회성

    - 손쉬운 병렬화

 

1. 원본 데이터의 수정이 없음.

말 그대로입니다.

OOP보단 FP에서 많이 강조하는 말인데 스트림은 원본 데이터를 훼손하거나 수정하지 않습니다.

 

2. 데이터 처리를 주목적으로 하는 인터페이스를 제공함, Lazy 베이스

데이터 처리를 목적으로 하는 인터페이스라기 보단 계산식의 차이입니다.

Lazy 베이스 구현과 함께 말씀드려야 할 것 같은데, Lazy부터 간단히 설명하고 넘어가겠습니다.

Lazy는 필요한 데이터를 그때그때 불러오는 방식입니다. 기본적으론 모든 값을 계산하고 적용하는 마치 ssr과 같은 시스템이라면 Lazy는 틀만 갖고 가서 필요할 때마다 값을 요청해 가져오는 csr과 흡사하죠.

stream은 이 부분에서 큰 차이를 나타냅니다. 컬랙션은 저장을 위한 타입이기에 모든 값을 한 번씩 계산하게 되죠.

stream은 Lazy방식이기에 계산에 필요 없는 데이터는 찾아보지도 않습니다. 그래서 조금 더 빠른 편입니다.

 

3. 저장공간을 독립적으로 갖지 않음, 거의 무한함

stream은 저장공간을 갖는 타입이 아닙니다.

좀 다른 계산식을 포함한 기능을 던져주는 인터페이스에 가깝죠. 그러다 보니 저장된 값에 붙여서 쓰게 됩니다.

저장된 곳에 붙여 쓰면 결국 자신은 붙이기만 하면 무한하게 데이터를 처리할 수 있는 거죠.

 

4. 간결함

코드를 보면 한눈에 아실 수 있습니다.

List<String> test = new ArrayList();
test.add("test1");
.
.
.

for (int i = 0; i < 10; i++) {
	System.out.println(test[i]);
}

test.stream()
	.foreach(System.out::println);

보다시피 매우 간결해집니다.

물론 stream을 감싸지 않고 foreach를 쓸 수도 있긴 합니다만... 그건 위의 내용을 보시면 될 것 같습니다.

 

5. 일회성

3번에서 말했듯이 저장공간 없이 그저 기능의 방향을 수정해 주는 느낌의 클래스이기에 계속 사용되는 것이 아닌 .stream() 이후엔 추가 삭제 모두 불가능합니다.

stream의 반복 작업이 끝나면 다시 해당 스트림으로 접근할 수도 없고요.

 

6. 손쉬운 병렬화

기본적인 stream은 stream타입으로 쓰지만 paralleStream으로 생성하여 사용하게 되면 병렬 처리 또한 가능합니다.

고작 타입 이름 조금 바꾼다고 가능해지니 매우 쉽게 병렬화가 가능하죠.

 

지금까지 이론적인 차이점을 알아봤다면 이제 실제 코드를 통한 성능을 비교해 보도록 하겠습니다.

제가 테스트를 잘하거나 자주 하는 편은 아니라 맹신 치는 마시고 참고만 해주시길 바랍니다.

List <String>에 test1 ~ test10000000까지 넣어두고 list의 foreach와 stream의 foreach를 활용해 getByte메서드를 돌렸습니다. 그리고 해당 작업들을 100번 돌려 평균치를 구했습니다.

 

모든 데이터를 getByte 하는 작업을 한번 수행할 때 평균적으로 아래만큼 걸렸습니다.

list의 foreach문은 247ms

stream의 foreach문은 232ms

 

테스트 코드는 아래의 링크나 아래의 코드를 통해 직접 해보실 수 있습니다.

https://github.com/koldin108902/learn-spring/tree/master/streamListTest

package org.example;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        long listResult = 0;
        long streamResult = 0;
        final long iterCount = 100;
        final long testCount = 10000000;

        for (int j = 0; j < iterCount; j++) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < testCount; i++) {
                list.add("test" + i);
            }
            long listBeforeTime = System.currentTimeMillis();
            list.forEach(String::getBytes);
            long listAfterTime = System.currentTimeMillis();

            listResult = listResult + (listAfterTime - listBeforeTime);

            long streamBeforeTime = System.currentTimeMillis();
            list.stream().forEach(String::getBytes);
            long streamAfterTime = System.currentTimeMillis();
            streamResult += (streamAfterTime - streamBeforeTime);
        }

        System.out.println("When \"" + testCount + "\" data is repeatedly processed \"" 
        				+ iterCount + "\" times, the average value (ms) is as follows");
        System.out.println("list = " + (listResult / iterCount));
        System.out.println("stream = " + (streamResult / iterCount));
        System.out.println("total \"" + (iterCount * testCount) + "\" times");
    }
}