이펙티브 자바 #item46 스트림에는 부작용없는 함수를 사용하라

임현규·2023년 5월 2일
0

이펙티브 자바

목록 보기
46/47
post-thumbnail

부작용 없는 함수, 순수 함수

stream pipe line은 함수형 프로그래밍 패러다임에 기초한다. 함수형 프로그래밍이란 상태가 아닌 인자에만 영향을 받아 변화하는 함수를 조합해서 프로그래밍하는 패러다임이다. 자세한건 해당 범위를 넘어가므로 생략한다.

stream의 패러다임은 계산을 일련의 변환으로 재구성하는 부분이다. 이때 함수는 이전 단계를 받아 다음 단계를 수행하는 순수 함수(상태에 관계 없이 input에 따라 output이 결정된다)여야 한다.

stream 패러다임을 이해하지 못한 예제

class Solution {

    public static void main(String[] args) {
        Map<String, Long> freq = new HashMap<>();
        String[] words = new String[]{"hello", "world", "hello", "world", "fire"};
        Arrays.stream(words)
            .forEach(word -> freq.put(word.toLowerCase(), freq.getOrDefault(word, 0L) + 1));
    }
}

forEach를 써서 for 구문 처럼 사용했다. 이 때 외부의 상태를 참조해서 로직을 수행하는데 이는 stream이 지향하는 함수형 프로그래밍에 맞지 않다. forEach는 그저 출력 결과나 디버깅용도 정도로만 사용하여야한다.

stream 패러다임을 이해한 예제

class Solution {

    public static void main(String[] args) {
        String[] words = new String[]{"hello", "world", "hello", "world", "fire"};
        Map<String, Long> freq = Arrays.stream(words)
            .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));
    }
}

words의 각 원소를 순회하며 종단 연산을 활용해 새로운 결과를 리턴한다.
인자를 활용해 새로운 정보를 리턴한다. 이 방식은 부작용이 없고 안전하다. 이러한 방식이 stream이 지향하는 패러다임을 잘 이해하고 수행한 것이다.

stream을 잘 사용한 예제들

빈도표에서 가장 흔한 단어 10개를 뽑아내는 파이프라인

        List<String> topTen = freq.keySet().stream()
            .sorted(Comparator.comparing((String key) -> freq.get(key)).reversed())
            .limit(10)
            .collect(toList());

문자열을 열거 타입 상수에 매핑하기

enum Operation {
    PLUS, MINUS, MULTIPLY, DIVIDE;
    
    private static final Map<String, Operation> operationMap = initOperationMap();
    
    private static Map<String, Operation> initOperationMap() {
        return Arrays.stream(Operation.values())
            .collect(Collectors.toMap(Operation::name, operation -> operation));
    }
}

각 키와 특정 원소를 연관짓는 맵 생성하기

class Solution {

    public static void main(String[] args) {
        Map<Artist, Album> toHits = albums.collect(
            Collectors.toMap(Album::artist, a -> a, BinaryOperator.maxBy(Comparator.comparing(Album::sales)))
        );
    }
}

toMap에는 3개의 인자를 받을 수 있다. key와 mapping 로직을 담당하는 람다 함수, merge에 필요한 BinaryOperator를 받을 수 있다.

위의 toHit는 Album에 있는 중복된 Artist마다 sales가 모두 다를 수 있다. 이때 충돌시 병함 함수를 BinaryOperator로 제공한다. 이를 이용해 같은 Artist에 대해서 큰 값으로 병합할 수 있다.

각 문자열 합치기

class Solution {

    public static void main(String[] args) {
        String[] strings = {"sun", "bed", "car"};
        String collect = Arrays.stream(strings).collect(Collectors.joining());
    }
}
profile
엘 프사이 콩그루

0개의 댓글