스트림을 사용해야 멋지게 처리할 수 있는 일이 있고, 반복 방식이 더 알맞은 일도 있음
수많은 작업이 이 둘을 조합했을 때 가장 멋지게 해결됨
스트림 API
- 스트림 API 는 다량의 데이터 처리 작업(순차적이든 병렬적이든)을 돕고자 자바 8에 추가되었다. 이 API가 제공하는 추상 개념 중 핵심은 두 가지
- 스트림은 데이터 원소의 유한 혹은 무한 시퀀스를 뜻함
- 스트림 파이프라인은 이 원소들로 수행하는 연산 단계를 표현하는 개념
- 스트림 파이프라인은 소스 스트림 → 하나 이상의 중간 연산 → 종단 연산
- 각 중간 연산은 스트림을 어떠한 방식으로 변환함
- 스트림 파이프라인은 지연 평가(lazy evaluation)됨. 평가는 종단 연산이 호출될 때 이뤄지며, 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않는다.
- 종단 연산이 없는 스트림 파이프라인은 아무 일도 하지 않는 명령어인 no-op과 같다
주의점
// Overuse of streams - don't do this! (page 205) public class StreamAnagrams { 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 class HybridAnagrams { 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); } }
- 너무 과용해서 사용하면 프로그램이 읽거나 유지보수하기 어려움
- 도우미 메서드(
alphabetize
)를 적절히 활용하여 코드를 구성하면 스트림 파이프라인에서 가독성이 높아짐
- 람다 매개변수의 이름은 주의해서 지어야 함. 람다에서는 타입 이름을 자주 생략하므로 매개변수 이름을 잘 지어야 스트림 파이프라인의 가독성이 유지됨
스트림으로 처리하기 어려운 경우
- 한 데이터가 파이프라인의 여러 단계를 통과할 때 이 데이터의 각 단계에서의 값들에 동시에 접근하기는 어려운 경우. 스트림 파이프라인은 일단 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃는 구조이기 때문