NMP-Study / EffectiveJava2022

Effective Java Study 2022
5 stars 0 forks source link

아이템 46. 스트림에서는 부작용 없는 함수를 사용하라 #46

Closed okhee closed 1 year ago

RulLu16 commented 1 year ago

스트림은 또 하나의 API가 아닌, 함수형 프로그래밍에 기초한 패러다임 스트림이 제공하는 표현력, 속도, (상황에 따라서는) 병령성을 얻으려면 API와 패러다임을 함께 이해 필요

스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성!

  • 각각의 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다!
  • 순수함수? = 오직 입력만이 결과에 영향을 주는 함수. 다른 가변상태를 참조하지 않으며, 함수 스스로도 다른상태를 변경하지 않는다!

스트림 패러다임을 이해하지 못한 채 API만 사용한 경우

        Map<String, Long> freq = new HashMap<>();

        List<String> wordList = new ArrayList<>();
        wordList.add("apple");
        wordList.add("lemon");
        wordList.add("banana");

        try (Stream<String> words = wordList.stream()) {
            words.forEach(word -> {
                freq.merge(word.toLowerCase(), 1L, Long::sum);
            });
        }

스트림을 제대로 활용해 빈도표를 초기화한 경우

        Map<String, Long> freq;

        List<String> wordList = new ArrayList<>();
        wordList.add("apple");
        wordList.add("lemon");
        wordList.add("banana");

        try (Stream<String> words = wordList.stream()) {
            freq = words.collect(groupingBy(String::toLowerCase, counting()));
        }

        freq.forEach((k,v)->System.out.println("key : " + k + " Count : " + v));

collector(수집기가 생성하는 객체)

빈도표에서 상위 2개를 뽑아내는 파이프라인

        List<String> topTwo = freq.keySet().stream()
                .sorted(comparing(freq::get).reversed())
                .limit(2)
                .collect(toList());

        topTwo.stream().forEach(v -> System.out.println(v));

toMap

예시1. 수집기를 사용하여 문자열을 열거 타입 상수에 매핑

import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toMap;

public enum Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }

    public abstract double apply(double x, double y);

    private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect(toMap(Object::toString, e -> e));

    public static Optional<Operation> fromString(String symbol) {
        return Optional.ofNullable(stringToEnum.get(symbol));
    }

}

예시2. 각 키와 해당 키의 특정 원소를 연관짓는 맵을 생섯하는 수집기

Map<Artist, Album> topHits = albums.collect(toMap(Album::article, a->a, maxBy(comparing(Album::sales))));

groupingBy

예시2. 다운스트림(downstream)수집기 명시한 경우

joining

String result= list.stream().collect(Collectors.joining()); System.out.println(result); // output : ABCD

result= list.stream().collect(Collectors.joining(",")); System.out.println(result); // output: A,B,C,D

result= list.stream().collect(Collectors.joining("-","[","]")); System.out.println(result); // output: [A-B-C-D]


## 결론
- 스트림 파이프라인 프로그래밍의 핵심은 부작용 없는 함수 객체에 있다. 
- 스트림뿐만 아니라, 스트림 관련 객체에 건네지는 모든 함수 객체가 부작용이 없어야 한다. 
- forEach는 수행한 계산 결과를 보고할 때만 이용해야 한다.
- 스트림을 올바로 사용하려면 collector를 잘 알아둬야 한다. 
- 수집기 팩터리 : toList, toSet, toMap, groupingBy, joining