스트림의 한계
•
스트림 API는 여러 기능들을 제공하여 사용자가 사용하길 원하는 많은 계산들을 하게 만들 수 있다. 하지만 스트림을 잘못 하용하면 가독성이 저하되고 유지보수가 힘들어지기 때문에 주의해서 사용하자.
Java
복사
•
이와 같은 코드를 스트림을 적용하면
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); // 결과 출력
}
Java
복사
•
이처럼 바꿀 수 있다. 다만 보면 알 수 있듯 스트림을 과하게 적용하여 가독성이 떨어진다.
try (Stream<String> words = Files.lines(dictionary)) {
words.collect(groupingBy(Anagrams::alphabetize)) // 애너그램을 수집
.values().stream() // 맵의 값들을 스트림으로 변환
.filter(group -> group.size() >= minGroupSize) // 충분히 큰 그룹만 필터링
.forEach(group -> System.out.println(group.size() + ": " + group)); // 결과 출력
}
/* alphabetize */
private static String alphabetize(String s) {
char[] a = s.toCharArray();
java.util.Arrays.sort(a);
return new String(a);
}
Java
복사
•
이처럼 적정 수준의 스트림을 사용하면 깔끔하게 표현될 수 있다.
•
기존 코드를 스트림으로 리팩터링을 할 때는 리팩터링 후 코드가 더 깔끔해질 때만 적용하자.
스트림의 람다식 vs 반복문의 코드블록
•
스트림 람다식의 함수 객체에서는 할 수 없지만, 반복문 내의 코드블록에서는 가능한 것들이 있다.
◦
람다식에서는 final이나 사실상 final인 변수들만 읽을 수 있고 지역변수를 읽는 게 불가능하지만, 코드 블록에서는 스코프 내의 지역변수를 읽고 수정할 수 있다.
◦
코드 블록에서는 return이나 continue, break를 통해 반복문을 종료하거나 건너뛰는게 가능하지만, 람다식에서는 불가능하다.
◦
코드 블록에서는 메서드 선언에 명시된 검사 예외를 던질 수 있지만, 람다식은 할 수 없다.
•
위 사항들의 로직을 수행해야한다면, 해당 코드는 스트림으로 수정하는게 올바르지 못한 선택일 가능성이 높다.
•
또한 스트림에서는 파이프라인을 통과하면서 데이터를 따로 저장하는게 아니라 해당 단계가 넘어가면 이전의 결과는 잃어버리기 때문에, 여러 단계의 연산 결과 데이터에 접근해야하는 상황이 있다면 스트림을 적용해선 안된다.
•
반면 스트림에 적용하기 적합한 코드들의 특징들은 다음과 같다.
◦
원소들의 시퀀스를 일관되게 변환한다.
◦
원소들의 시퀀스를 필터링한다.
◦
원소들의 시퀀스를 하나의 연산으로 결합한다.
◦
원소들의 시퀀스를 공통된 속성을 기준으로 묶어 컬렉션으로 저장한다.
◦
원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.
결론
•
스트림과 반복문 중 더 나은 선택을 해서 적용하자.
•
어느 쪽이 더 나은지 모르겠다면, 둘 다 구현해보고 선택하자.