[아이템 45] 스트림은 주의해서 사용하라
스트림: 데이터 원소의 유한 또는 무한 시퀀스
스트림 파이프라인: 원소들을 수행하는 연산 단계를 표현하는 개념
대표적인 스트림의 원소들의 출처
ㄱ. 컬렉션
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
ㄴ. 배열
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
ㄷ. 파일
public static Stream<Path> list(Path dir) throws IOException
ㄹ. 정규표현식 패턴매처 - Pattern.splitAsStream
public Stream<String> splitAsStream(final CharSequence input) {
ㅁ. 난수생성기 - Random.doubles()
public DoubleStream doubles() {
return StreamSupport.doubleStream
(new RandomDoublesSpliterator
(this, 0L, Long.MAX_VALUE, Double.MAX_VALUE, 0.0),
false);
}
ㅁ. 기본값 - IntStream, DoubleStream, LongStream
스트림 파이프라인의 특징
스트림 파이프라인은 소스스트림 -> (중간연산) -> 종단연산 으로 이루어진다
중간연산을 합친 다음에 합쳐진 중간연산을 최종 연산으로 한번에 처리 -> Lazy
중간연산
스트림을 변환(transform). 결과스트림의 원소 타입은 시작 스트림의 원소 타입과 같을 수도, 다를 수도 있다.
ㄱ. sorted
Stream<Integer> sorted = operands.stream().sorted();
ㄴ. filter
Stream<Integer> integerStream = operands.stream().filter((value) -> value > 2);
ㄷ. map
Stream<Double> doubleStream = operands.stream().map(Double::new);
종단연산
마지막 중간 연산의 스트림에 최후의 연산. 1개 이상의 중간연산들은 계속합쳐진 후 종단연산 시 수행된다.
즉, 스트림 파이프라인은 지연평가(lazy evaluation)된다.
- 종단연산이 없는 파이프라인은 어떤 연산도 수행되지 않는다.
- 지연평가는 무한스트림을 다룰 수 있게 해주는 열쇠다.
- 지연평가를 하지 않는다면 중간연산은 끝나지 않는다.
스트림은 주의해서 사용해야한다?
대부분의 연산은 스트림으로도 구현할 수 있다.
하지만 과도한 스트림은 읽기 어렵고 유지보수가 힘들다. 또, 성능상 좋지 않을 수도 있다.
다음의 코드는 과도한 스트림때문에 읽기 어렵다.
public void anagram(){
List<String> dictionary = Arrays.asList("Lee MinHyeong", "Seo Jaeyeon", "CRUD", "Minjeong", "PCI", "ICP");
dictionary.stream()
.collect(groupingBy( // Map의 Key
word -> word.chars().sorted() // char 배열을 정렬해서
.collect(StringBuffer::new, (stringBuffer, value) -> stringBuffer.append((char)value), StringBuffer::append).toString()))
.values().stream() // Map의 Value들
.filter(group -> group.size() >= 2)
.map(group -> group.size() +": " + group)
.forEach(System.out::println);
}
무조건 스트림만 쓰는 것이 아닌 절중 지점을 찾아야한다.
또, 스트림의 변수는 람다형이기 때문에 스트림 변수를 이해하기 쉽게 짓는 것도 중요하다.
자바는 char용 스트림을 지원하지 않는다
IntStream chars = "Hello".chars(); // IntStream이 반환된다.
스트림을 사용하지 못할 때 - 함수객체 사용 관점에서
- 지역변수를 읽고 수정할 필요가 있을 때
- 람다의 변수는 사실상 final이다.
- 람다는 return, break, continue 문이 불가능하다
스트림이 적절할 때
- 원소들의 시퀀스를 일관성 있게 변환할 때
- 원소들의 시퀀스를 필터링할 때
- 원소들의 시퀀스를 연산 후 결합할 때
- 원소들의 시퀀스를 모을 때
- 원소들의 시퀀스 중 특정 조건을 만족하는 원소를 찾을 때
스트림으로 처리하기 어려울 때
- 원본 스트림을 계속 써야할 때
- 스트림은 중간연산을 지나고 나면 원래의 스트림을 잃는다. 파이프라인의 순서를 바꿈으로써 해결할 수 있는지 고민해보자.
[아이템 46] 스트림에서는 부작용 없는 함수를 사용하라
스트림은 함수형 프로그래밍에 기초한 패러다임이다
모던 자바 인 액션 발췌
함수를 값으로
메서드를 값으로 취급하여 활용성을 높인다.(일급 값)
- 메서드 참조
- 람다(익명함수)
- 함수형 프로그래밍: 함수를 일급값으로 넘겨주는 프로그래밍
- 스트림
스트림 패러다임의 핵심
각 변환 단계는 "순수함수"로 구성되어양 한다.
순수함수: 입력만이 출력엥 영향을 준다
다양한 스트림 연산
forEach
forEach 연산은 스트림 계산 결과를 보고할 때만 사용하고, 계산할 때는 사용하지 말자.
-> forEach 연산에서 계산하는 것은 덜 스트림스럽다.
Collector
수집기(collector)를 잘 활용하자.
- 축소(reduction) 전략을 캡슐화한 블랙박스 객체
- reduction: 원소들을 객체 하나에 취합한다는 뜻
- 맵 수집기(toMap)
ㄱ. 각각 KeyMapper, ValueMapper 를 인수로하는 가장 간단한 맵 수집기. Key 가 중복되면 Exception.
toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.
@DisplayName("맵수집기 - 각 원소당 하나의 키")
@Test
void toMap_Test() {
Map<String, Operation> expectedMap = new HashMap<>();
expectedMap.put("PLUS", Operation.PLUS);
expectedMap.put("MINUS", Operation.MINUS);
expectedMap.put("DIVIDE", Operation.DIVIDE);
Map<String, Operation> collect = Stream.of(Operation.values())
.collect(toMap(Objects::toString, e -> e));
// Map<String, Operation> failCollect = Stream.of(Operation.values())
// .collect(toMap(e -> "SameKey", e -> e)); // java.lang.IllegalStateException: Duplicate key SameKey (attempted merging values PLUS and MINUS)
assertThat(collect).isEqualTo(expectedMap);
}
ㄴ. KeyMapper, ValueMapper, 두 원소가 충돌했을 때의 병합함수
toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)
Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.
@DisplayName("맵수집기 - 인수가 세개")
@Test
void toMap_Test_Merge() {
Map<String, Operation> expectedMap = new HashMap<>();
expectedMap.put("SameKey", Operation.PLUS);
Map<String, Operation> collect = Stream.of(Operation.values())
.collect(toMap(e -> "SameKey", e -> e, (a, b) -> a));
assertThat(collect).isEqualTo(expectedMap);
}
ㄷ. map의 구현체를 정할 수 있다.
toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.
@DisplayName("맵수집기 - 인수가 네개")
@Test
void toMap_Test_Four() {
Map<Operation, String> expectedMap = new EnumMap<>(Operation.class);
expectedMap.put(Operation.PLUS, "PLUS");
expectedMap.put(Operation.MINUS, "MINUS");
expectedMap.put(Operation.DIVIDE, "DIVIDE");
EnumMap<Operation, String> collect = Stream.of(Operation.values())
.collect(toMap(e -> e, Object::toString, (a, b) -> a, () -> new EnumMap<>(Operation.class)));
assertThat(collect).isEqualTo(expectedMap);
}
2. groupingBy
ㄱ. classfier(분류함수)를 받고 카테고리로 묶은 Map을 담은 수집기 반환. 값은 List
groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.
@DisplayName("groupBy 의 기본쓰임")
@Test
void groupByBasic() {
//given
List<String> dictionary = Arrays.asList("apple", "apartment", "banana", "bigbang", "count", "cleancode");
//when
Map<Character, List<String>> collect = dictionary.stream()
.collect(groupingBy(word -> word.toLowerCase().charAt(0)));
}
ㄴ. 값을 리스트 외 다른 타입을 반환하기 위해서는 downstream명시. 다운스트림 수집기의 역할은 해당카테고리의 원소들을 담은 스트림으로부터 값을 생성하는 일
groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.
@DisplayName("groupby + downstream")
@Test
public void groupByDownStream() {
//given
List<String> dictionary = Arrays.asList("apple", "apartment", "banana", "bigbang", "count", "cleancode");
//when
Map<Character, Long> collect = dictionary.stream()
.collect(groupingBy(word -> word.toLowerCase().charAt(0), counting()));
}
ㄷ. 다운스트림, 맵 팩터리 지정
-
ㄴ에 의해 맵팩터리 인수는 세번째에 와야하짐나 두번째에 온다. 점층적 인수 목록 패턴에 어긋난다.
groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream) Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.
@DisplayName("groupby + 맵팩터리")
@Test
void groupByMapFactory() {
//given
List<String> dictionary = Arrays.asList("apple", "apartment", "banana", "bigbang", "count", "cleancode");
//when
Map<Character, Long> collect = dictionary.stream()
.collect(groupingBy(word -> word.toLowerCase().charAt(0), TreeMap::new, counting()));
}
3. 그 외 다양한 메서드
elements.stream().collect(Collectors.summingInt(a -> a * a));
// mapToInt().sum() 과 동일
IntSummaryStatistics collect = elements.stream().collect(Collectors.summarizingInt(a -> a + a));
//IntSummaryStatistics{count=6, sum=42, min=2, average=7.000000, max=12}
joining, reducing, filtering, mapping, flatMapping, collectingAndThen
Integer collect = elements.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collection::size));
// collect한 뒤 변환까지
'Reading Record > 이펙티브자바' 카테고리의 다른 글
[ 아이템 2 ] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2020.06.11 |
---|---|
[아이템 42] 익명 클래스보다는 람다를 사용하라 (0) | 2020.06.09 |
[아이템 37] ordinal 인덱싱 대신 EnumMap을 사용하라 (1) | 2020.04.18 |
[아이템 36] 비트 필드 대신 EnumSet을 사용하라 (0) | 2020.04.17 |
[아이템 35] ordinal 메소드 대신 인스턴스 필드를 사용하라 (0) | 2020.04.17 |