조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.
1. 스트림은 주의해서 사용하라
스트림
데이터 원소의 시퀀스
- 유한 혹은 무한
- 컬렉션, 배열, 파일, 정규표현식 패턴 매처 등
- 다량의 데이터 처리 작업을 돕고자 추가됨 (Java 8)
파이프라인
- 원소들로 수행하는 연산 단계
- 순차적으로 수행됨
- 플루언트 API로 메서드 연쇄를 지원
시작
- 소스 스트림으로 시작
중간연산
- 한 스트림을 다른 스트림으로 변환함
종단 연산
- 마지막 중간 연산이 내놓은 스트림에 최후의 연산을 수행함
- 지연 평가
- 무한 스트림이 이루어지도록 함
주의사항
재사용 불가
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);
// 재사용하려 하면 예외 발생
stream.forEach(System.out::println); // IllegalStateException
- 스트림은 일회성
- 한번 사용된 스트림은 다시 사용할 수 없음
원본 수정 금지
List<String> list = Arrays.asList("a", "b", "c");
list.stream().map(String::toUpperCase); // 원본 리스트는 변경되지 않음
System.out.println(list); // [a, b, c]
- 불변성을 유지
- 데이터를 변환하려면 새로운 스트림이나 컬렉션을 생성해야 함
외부 상태 변경 금지
List<Integer> numbers = Arrays.asList(1, 2, 3);
int[] sum = {0};
numbers.stream().forEach(n -> sum[0] += n); // 외부 상태 변경
System.out.println(sum[0]); // 위험
- 순수 함수형 프로그래밍을 지향
- 사이드 이펙트 유발 금지
2. 스트림에서는 부작용 없는 함수를 사용하라
- 스트림은 함수형 프로그래밍에 기초한 패러다임
- 스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분
순수 함수
- 각 변환 단계는 이전 단계의 결과를 받아 처리하는 순수 함수
- 순수 함수는 다른 가변 상태를 참조하지 않고, 함수 스스로도 다른 상태를 변경하지 않음
forEach
- 스트림 계산 결과를 보고할 때만 사용하기
- 계산에 사용하지 말기
Collectors
toList()
toSet()
toMap()
toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
- keyMapper: 키를 생성하는 함수.
- valueMapper: 값을 생성하는 함수.
- mergeFunction: 키 충돌 시 값을 병합하는 함수.
- mapFactory: 사용할 Map의 구현체를 생성하는 함수.
List<String> items = Arrays.asList("apple", "banana", "apple");
Map<String, Integer> itemCount = items.stream()
.collect(Collectors.toMap(
item -> item, // keyMapper: 단어 자체를 키로 사용
item -> 1, // valueMapper: 각 단어를 1로 매핑
Integer::sum, // mergeFunction: 같은 키가 있으면 합침
HashMap::new // mapFactory: HashMap 사용
));
System.out.println(itemCount); // {apple=2, banana=1}
- 스트림 요소를 키-값 쌍으로 변환해 Map 생성
grouppingBy()
groupingBy(classifier, downstreamCollector, mapFactory)
- classifier: 그룹화 기준을 지정하는 함수.
- downstreamCollector: 그룹화 후 데이터를 어떻게 처리할지 지정하는 컬렉터.
- mapFactory: 결과로 생성될 Map의 구현체를 지정.
List<String> items = Arrays.asList("apple", "banana", "cherry", "apricot");
Map<Character, List<String>> groupedByFirstChar = items.stream()
.collect(Collectors.groupingBy(
item -> item.charAt(0), // classifier: 첫 글자를 기준으로 그룹화
Collectors.toList(), // downstreamCollector: 그룹화된 데이터 리스트로 수집
TreeMap::new // mapFactory: TreeMap 사용
));
System.out.println(groupedByFirstChar); // {a=[apple, apricot], b=[banana], c=[cherry]}
- 데이터를 특정 기준으로 그룹화
partitionBy()
partitioningBy(classifier)
- classifier: 데이터를 참/거짓으로 분류하는 함수.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(
num -> num % 2 == 0 // classifier: 짝수와 홀수로 분리
));
System.out.println(partitioned); // {false=[1, 3, 5], true=[2, 4, 6]}
- 데이터를 두 개의 그룹으로 분리
joining()
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
- 구분자를 포함하여, 앞뒤에 접두사와 접미사 지정
List<String> words = Arrays.asList("apple", "banana", "cherry");
String result = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(result); // [apple, banana, cherry]
'Java' 카테고리의 다른 글
[Effective Java] 8-1. 메서드 (2) | 2024.12.31 |
---|---|
[Effective Java] 7-3. 람다와 스트림 (0) | 2024.12.31 |
[Effective Java] 7-1. 람다와 스트림 (1) | 2024.12.31 |
[Effective Java] 6-3. 열거 타입과 애노테이션 (0) | 2024.12.31 |
[Effective Java] 6-2. 열거 타입과 애노테이션 (0) | 2024.12.31 |