본문 바로가기
프로그래밍/Java

[java] Java와 Lazy evaluation - Java8 Stream

by 사바라다 2021. 4. 2.

안녕하세요. 이전시간에 우리는 Lazy evaluation에 대해서 알아보는 시간을 가졌었습니다. 오늘은 이어서 Java8의 Stream의 Lazy Evaluation에 대해서 알아보는 시간을 가져보겠습니다.

우리는 이전 시간 [Java] Java와 Lazy Evaluation 포스터에서 Lazy Evaluation의 장점을 필요하지 않는 연산을 하지 않는다라고 이야기했습니다. 그리고 Stream은 Lazy Evaluation을 사용하는 대표적인 연산방식입니다.

예제

아래와 같은 로직이 있다고 해보겠습니다.

1 ~ 30까지의 숫자의 List가 있습니다. 이 숫자에서 3의 배수의 숫자만을 걸러내고 10을 곱한 값중 앞에서 3가지만 취하도록 하겠습니다.

이런 로직을 가지는 메서드를 만든다고 해보겠습니다.

Collection

Collection을 이용하여 만든다고 하면 아래와 같은 코드가 만들어질 것입니다.

service는 사용하는 client 같은 메서드이며 filter 로직이 예제로 사용하는 로직입니다. 파라미터인 integers를 for을 이용하여 loop를 돌며 3의 배수인 값을 찾아냅니다. 그리고 result에 3개이상의 값이 이미 있는지 판단 한 후 result에 값을 추가합니다. 모든 loop가 마무리되면 result를 반환합니다.

public void service() {
    List<Integer> integers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    List<Integer> filteredInteger = filter(integers);
    System.out.println("result = " + Arrays.toString(filteredInteger.toArray()));
}

private List<Integer> filter(List<Integer> integers) {

    List<Integer> result = new ArrayList<>();

    int count = 0;
    for (Integer integer : integers) { // integers의 size 만큼 loop
        System.out.println("숫자 걸러내기_1 integer = " + integer);
        if (integer % 3 == 0 && count++ < 3) { // filter
            result.add(integer * 10);
        }
    }

    return result;
}

결과를 보도록 하겠습니다. List인 integers 안에는 15개의 엘리먼트가 있었으며 그 엘리먼트를 모두 한번씩은 거쳐가는 것을 알 수 있습니다.

숫자 걸러내기_1 integer = 1
숫자 걸러내기_1 integer = 2
숫자 걸러내기_1 integer = 3
숫자 걸러내기_1 integer = 4
숫자 걸러내기_1 integer = 5
숫자 걸러내기_1 integer = 6
숫자 걸러내기_1 integer = 7
숫자 걸러내기_1 integer = 8
숫자 걸러내기_1 integer = 9
숫자 걸러내기_1 integer = 10
숫자 걸러내기_1 integer = 11
숫자 걸러내기_1 integer = 12
숫자 걸러내기_1 integer = 13
숫자 걸러내기_1 integer = 14
숫자 걸러내기_1 integer = 15
result = [30, 60, 90]

Stream

이번에는 Stream을 이용하여 메서드를 만들어 보도록 하겠습니다. streamFilter를 보시면 filter, map, limit를 사용하고 있습니다. filter는 조건을 통해 값을 거르고, map은 변형, 그리고 limit는 그 숫자를 제한하는 함수입니다. 그리고 마지막 collect를 통해 그 결과를 List로 반환합니다.

public void service() {
    List<Integer> integers_2 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    List<Integer> filteredInteger_2 = streamFilter(integers_2);
    System.out.println("result = " + Arrays.toString(filteredInteger_2.toArray()));
}

private List<Integer> streamFilter(List<Integer> integers) {
    return integers.stream()
            .filter(integer -> { // filter 처리
                System.out.println("숫자 걸러내기_2 integer = " + integer);
                return integer % 3 == 0;
            })
            .map(integer -> integer * 10) // 변형
            .limit(3) // 갯수 제한
            .collect(Collectors.toList());
}

이렇게 로직을 구성했을 때 연산결과는 아래와 같습니다. Collection을 일반적으로 이용한것과는 다르게 Stream을 이용했을 때는 총 9번의 연산만이 있었음을 알 수 있었습니다. 이게 바로 Stream이 가진 Lazy Evaluation을 통해서 일어난 부분입니다.

숫자 걸러내기_2 integer = 1
숫자 걸러내기_2 integer = 2
숫자 걸러내기_2 integer = 3
숫자 걸러내기_2 integer = 4
숫자 걸러내기_2 integer = 5
숫자 걸러내기_2 integer = 6
숫자 걸러내기_2 integer = 7
숫자 걸러내기_2 integer = 8
숫자 걸러내기_2 integer = 9
result = [30, 60, 90]

Stream은 연산을 곧바로 수행하지 않습니다. 마지막에 어떤 연산이 필요로하는가를 판단한 후 연산을 시작합니다. 위의 예제에서는 collect 함수를 실행할 때 실제 연산이 실행되는데 이전에 limit가 있다라는 사실을 이미 알고 있게되는 것입니다. 따라서 연산 결과가 3개가 나오면 더이상 연산을 하지 않고 종료할 수 있는 것입니다.

마무리

오늘은 이렇게 java stream의 lazy evaluation에 대해서 알아보는 시간을 가져보았습니다.

감사합니다.

참조

codemotion_lazy-java

slideshare_mariofusco_lazy-java

stackoverflow_does-java-have-lazy-evaluation

댓글