Skip to content

Latest commit

 

History

History
147 lines (101 loc) · 5.15 KB

45_스트림은_주의해서_사용하라.md

File metadata and controls

147 lines (101 loc) · 5.15 KB

아이템 45. 스트림은 주의해서 사용하라

용어 정리

  • 스트림 : 데이터 원소의 유한 혹은 무한 시퀀스
  • 스트림 파이프 라인 : 이 원소들로 수행하는 연산 단계를 표현

스트림 특징

람다식으로 요소 처리 코드를 제공

내부 반복자를 사용하므로 병렬처리 쉬움

  • 외부 반복자
    • 컬렉션의 요소를 가져오는 것에서 처리하는 것까지 모두 개발자가 작성
    • ex) for, while-iterator
  • 내부 반복자
    • 어떻게 요소를 반복시킬 것인가는 컬렉션 내부 에서 일어난다
    • 개발자는 요소당 처리할 코드에만 집중 할 수 있다.
    • 병렬처리가 컬렉션 내부에서 처리되므로 간편함

스트림 파이프라인

// �순서
(소스 스트림) -> (중간 연산) -> (종단 연산)

특징

  • 중간 연산은 스트림을 어떠한 방식으로 변환한다
    • ex) 필터링, 매핑, 정렬, 그룹핑 ...
  • 종단 연산은 중간 연산의 결과물에 최종 연산을 한다
    • ex) 합계, 평균, 카운팅, 최대값, 최소, 컬렉션 담기, 특정원소선택 ...
  • 종단 연산이 시작되기 전까지 중간 연산는 지연(lazy)된다
    • 종단 연산이 시작되면 컬렉션 요소가 중간 스트림에서 처리되고 종단연산까지 오게됨

예시 (따라하지말것)

  • 스트림을 과용하면 읽기 어렵고 유지보수가 힘들다.
public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(
                    groupingBy(word -> word.chars().sorted()
                            .collect(StringBuilder::new,
                                    (sb, c) -> sb.append((char) c),
                                    StringBuilder::append).toString()))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .map(group -> group.size() + ": " + group)
                    .forEach(System.out::println);
        }
    }

예시 (적절히 사용)

  • 적절히 활용하면 위 예시와 같은 동작을 하는데 깔끔하고 명료해진다.
public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -> alphabetize(word)))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .forEach(g -> System.out.println(g.size() + ": " + g));
        }
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }

1. 람다에서는 매개변수 이름을 잘 지어야 가독성이 유지된다.

  • ex) 위 예시에서 스트림 변수 이름을 words로 지어 명확히함

2. 스트림 파이프라인에서 도우미 메서드를 적절히 활용하는건 중요하다.

  • 연산에 적절한 이름을 지어주고 세부 구현을 주 프로그램 로직 밖으로 빼내서 가독성을 높이자.
  • ex) 위 예시에서 단어 철자를 알파벳순으로 정렬하는 일을 별도 메서드인 alphabetize

3. char 값을 처리할때는 스트림을 삼가하는 편이 낫다.

  • 자바가 char용 스트림을 지원하지 않아 잘못 구현할 가능성 크다.
  • ex) 위 예시 alphabetize 메서드

기존 코드는 스트림을 사용하도록 리팩터링하되, 새 코드가 더 나아보일 때만 반영하자.

  • 스트림으로 바꾸는게 가능해도 가독성, 유지보수성이 안좋아 질수도 있기떄문
  • 앞 예시처럼 스트림, 반복문 적절히 조합하는게 최선

스트림 사용 못할때

  • 스트림의 함수 객체로는 할 수 없지만 반복 코드의 코드블록로 할 수있는 일이 있다.

람다에서는 지역변수를 수정못한다.

  • 람다에서는 final인 변수만 읽을 수 있다.

람다에서는 return, break, continue 문 사용할 수 없다.


스트림 적절할때

  • 원소들의 시퀀스를 일관성 있게 변환할 때
  • 원소들의 시퀀스를 필터링할 때
  • 원소들의 시퀀스를 연산 후 결합할 때
  • 원소들의 시퀀스를 모을 때
  • 원소들의 시퀀스 중 특정 조건을 만족하는 원소를 찾을 때

스트림 처리 어려울때

  • 원본 값을 계속 써야할 때
    • 스트림은 중간 연산을 지나서 다른 값에 매핑되면 원래 값을 잃는 구조다.
    • 파이프라인의 순서를 바꿈으로써 해결할 수 있는지 고민해보자.

결론

  • 스트림 또는 반복이 좋은 경우가 있다. (많은 경우는 조합하는 경우가 낫다)
  • 스트림과 반복 중 어느 쪽이 나은지 확신하기 어렵다면 둘 다 해보고 더 나은 쪽을 선택하자.