[자바] for-loop와 Stream forEach는 뭐가 다른가요?

skyepodium·2022년 3월 21일
0
post-custom-banner
  1. forEach 순회중 중단 불가
  2. forEach 동시성 보장 필요

개요

개인적으로 Stream의 forEach가 요즘 트렌드 같고, 코드양도 상대적으로 적어, 알고리즘을 풀때 자주 사용했습니다.

JavaScript를 사용할 때 forEach를 사용했고 때문에 큰 문제가 없을 것으로 생각했지만

이렇게 하면 안된다고 느끼게 되었습니다.

for-loop와 Stream forEach의 차이점은 다음 2개입니다.

  1. forEach 순회중 중단 불가
  2. forEach 동시성 보장 필요

1. forEach 순회중 중단 불가

1) 문제

예를 들어 주어진 배열에서 target과 일치하는 원소를 찾고있습니다.

  • 1) 원소가 있으면 해당 원소 반환
  • 2) 원소가 없으면 -1 반환

2) 코드

다음과 같이 forEach 내부에서 break, return 을 사용하면 에러가 발생합니다.

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

class Main {
    public static void main(String[] args) {

        // 주어진 배열에서 1) target이 있으면 target을 2) 없으면 -1을 반환합니다.
        int[] l = {0, 1, 2, 3, 4, 5};
        int target = 3;

        System.out.println("for-loop 결과: " + forLoop(target, l)); // for-loop 결과: 3
        System.out.println("forEach 결과: " + forEach(target, l)); // forEach 결과: 3
    }

    public static int forLoop(int target, int[] l) {
        // 주어진 배열을 순회합니다.
        for(int i=0; i<l.length; i++) {
            if(l[i] == target) {
                // 타켓을 찾으면 반복문을 중단하고, 값을 바로 반환합니다.
                return l[i];
            }
        }
        return -1;
    }

    public static int forEach(int target, int[] l) {
        // 멀티쓰레드 환경에서 동시성을 보장하기 위해 Atomic 변수를 사용합니다.
        AtomicInteger i = new AtomicInteger(-1);
        Arrays.stream(l).forEach(x -> {
            if(x == target) {
                i.set(target);
                // 타켓을 찾아도 forEach를 중단할 수 없습니다.
                // return x;
                // break;
            }
        });
        return i.get();
    }
}

2. forEach 동시성 보장 필요

1) 문제

끝말잇기를 하고 있고 틀리는 경우 바로 이전 단어와, 현재 단어 를 반환합니다.

2) 코드

forEach 내부에서는 동시성이 보장되지 않아, 외부의 변수를 변경하려고 하면 에러가 발생합니다.

이러한 경우 atomic 변수를 사용합니다. atomic은 자바 멀티스레드 환경에서 동시성을 보장하기 위해 사용하는 변수로 synchronized 대신 CAS(Compare and Swap)알고리즘을 사용합니다.

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

class Main {
    public static void main(String[] args) {

        String[] words = {"apple", "elephant", "tree", "banana", "airplane"};

        System.out.println("for문 결과: " + forLoop(words)); // for문 결과: prev: tree cur: banana
        System.out.println("forEach 결과: " + forEach(words)); // forEach 결과: prev: tree cur: banana
    }

    public static String forLoop(String[] words) {
        String prev = "";
        for(String cur: words) {
            if(!prev.equals("") && prev.charAt(prev.length()-1) != cur.charAt(0)) {
                return "prev: " + prev + " cur: " + cur;
            }

            prev = cur;
        }
        return "None";
    }

    public static String forEach(String[] words) {
        // 동시성을 보장하기 위해 atomic 변수를 사용합니다.
        AtomicReference<String> prev = new AtomicReference<>();
        AtomicReference<String> res = new AtomicReference<>();

        Arrays.stream(words).forEach(cur -> {
            if(!prev.toString().equals("") && prev.toString().charAt(prev.toString().length()-1) != cur.charAt(0)) {
                res.set("prev: " + prev + " cur: " + cur);
            }

            prev.set(cur);
        });
        return res.toString();
    }
}

3. Atomic 변수 안써도 forEach에서 변경 가능한데?

다음 2가지와 같이

    1. map, set, list 에 넣는 경우
    1. 배열 요소에 인덱스로 접근하는 경우
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

class Main {
    public static void main(String[] args) {
        // 1. 배열
        String[] words = {"apple", "elephant", "tree", "tree"};

        // 2. 컨테이너
        Map<String, Integer> m = new HashMap<>();
        Set<String> s = new HashSet<>();
        List<String> l = new ArrayList<>();
        String[] a = new String[words.length];

        AtomicInteger i = new AtomicInteger(0);
        Arrays.stream(words).forEach(x -> {
            // 1. map, set, list 에 넣는 경우
            m.put(x, m.getOrDefault(x, 0) + 1);
            s.add(x);
            l.add(x);
            
            // 2. 배열 요소에 인덱스로 접근하는 경우
            a[i.get()] = x;
            i.set(i.get() + 1);
        });

        m.forEach((key, value) -> System.out.println(key + " " + value));
        s.forEach(System.out::println);
        l.forEach(System.out::println);
        Arrays.stream(a).forEach(System.out::println);
    }
}
profile
callmeskye
post-custom-banner

0개의 댓글