스트림 패러다임
스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 부분이다. 이때 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수 함수이여야 한다.
순수 함수
오직 입력만이 결과에 영향을 주는 함수
스트림의 잘못된 사용 예시
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
})
}
- 위 코드는 외부 상태(빈도표)를 수정하는 람다를 실행하면서 문제가 생긴다. (merge)
- forEach 연산은 스트림 계산 결과를 보여주는 일 이상을 하지 않는 것이 좋다.
잘 사용한 예시
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words.collect(groupingBy(String::toLowerCase, counting()));
}
java.util.stream.Collectors
Collector 인터페이스는 스트림의 원소들을 객체 하나에 취합하는 API들을 가지고 있다. Collector가 생성하는 객체는 일반적으로 컬렌션이며, toList(), toSet(), toCollection()이 있다.
List<String> topTen = freq.keySet().stream()
.sorted(comparing(freq::get).reversed())
.limit(10)
.collect(toList());
comparing 메서드는 키 추출 함수를 받는 비교자 생성 메서드이다.
Collectors의 맵 취합 기능
Collector의 많은 메서드가 스트림을 맵으로 취합하는 기능이며, 진짜 컬렉션에 취합하는 것보다 훨씬 복잡하다.
toMap(keyMapper, valueMapper)
스트림 원소를 키에 매핑하는 함수와 값에 매핑하는 함수를 인수로 받는다.
private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect(toMap(Object::toString, e -> e));
이러한 형태의 간단한 toMap은 스트림의 각 원소가 고유한 키에 매핑되어 있을 때 적합하다. 만약 스트림 원소 다수가 같은 키를 사용한다면 파이프라인이 IllegalStateException을 던진다.
복잡한 형태의 toMap
toMap에 키 매퍼와 값 매퍼는 물론 병합 함수까지 제공할 수 있다. 병합 함수의 형태는 BinaryOperator이다. 같은 키를 공유하는 값들은 이 병합 함수를 사용해 기존 값에 합쳐진다.
toMap(kepMapper, valueMapper, (oldVal, newVal) -> newVal);
또 인수 3개를 받는 toMap은 어떤 키와 그 키에 연관된 원소들 중 하나를 골라 연관 짓는 맵을 만들 때 유용하다.
Map<Artist, Album> toHits = albums.collect(toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));
groupingBy
이 메서드는 입력으로 분류 함수를 받고 출력으로는 원소들을 카테고리별로 모아 놓은 맵을 담은 수집기를 반환한다.
minBy, maxBy
인수로 받은 비교자를 이용해 스트림에서 값이 가장 작은 혹은 가장 큰 원소를 찾아 반환한다.
public class StreamMinMax {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 10, 15, 26, 30, 100));
Integer min = nums.stream().min(Comparator.comparingInt(a -> a)).get();
Integer max = nums.stream().max(Comparator.comparingInt(a -> a)).get();
System.out.println(min);
System.out.println(max);
}
}
Joining
CharSequence 인스턴스의 스트림에만 적용할 수 있다.
- 매개변수가 없는 joining은 단순히 원소들을 연결하는 수집기를 반환한다.
- 인수 하나짜리 joining은 CharSequence 타입의 구분문자를 매개변수로 받는다. 연결 부위에 이 구분문자를 삽입한다.
- 인수 3개짜리 joining은 구분문자에 더해 접두문자와 접미문자도 받는다.
public class JoiningPractice {
public static void main(String[] args) {
List<String> str = new ArrayList<>(List.of("1", "2", "#"));
String result = str.stream().collect(joining());
System.out.println(result); // 12#
String result2 = str.stream().collect(joining(","));
System.out.println(result2); // 1,2,#
String result3 = str.stream().collect(joining(",", "[", "]"));
System.out.println(result3); // [1,2,#]
}
}