
스트림이란?
- 컬렉션(배열 포함)의 요소를 하나씩 참조해서 람다식으로 처리할 수 있는 반복자.
- 병렬처리도 쉽게 가능하기 때문에(내부 반복자 이용) 편리하다.
- 병렬처리는 한가지 작업을 서브 작업으로 나눠서 분리되어서 처리하는 방식. (parallel만 붙이면 된다.)
- 오리지날 스트림 → (필터링 처리) 중간 스트림 → (매핑 처리) 중간 스트림 → 집계 처리 결과물
Collectors 기능
- 스트림 요소를 하나의 값으로 리듀스하고 요약
- 요소 그룹화
- 요소 분할
Reducing and Summarize
collect(Collectors.counting())
→stream().count()
로 변경 가능collect(summingInt(~~~))
값 더하기collect(averagingInt(~~~))
값 평균collect(summarizingInt(~~~))
모든 정보 수집collect(joining(", "))
문자열 합치기-
collect(reducing(0, Student::getScore, (i, j) -> i+j))
여기서
reduce()
를 바로 사용하는 것과collect()
를 이용하는 리듀싱에는 어떤 점이 차이가 있을까?collect
메서드는 도출하려는 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드인 반면reduce
는 두 값을 하나로 도출하는 불변형 연산이라는 점에서 의미론적인 문제가 일어난다. 여러 스레드가 동시에 같은 데이터 구조체를 고치면 리스트 자체가 망가져버리므로 리듀싱 연산을 병렬로 수행할 수 없다는 것이 문제인데 이 문제를 해결하려면 매번 새로운 리스트를 할당하는 방법으로 해야하는데 그러기에는 성능이 너무 좋지 않다. 이럴 때는collect()
를 사용하자!이런 방법도 가능 !!
collect(reducing(0, Student::getScore, Integer::sum))
여기서 만약 그냥 학생의 성적의 합만 계산한다면 students.stream().mapToInt(Student::getScore).sum()
이 가장 가독성도 좋고 자동 언박싱 과정을 피할 수 있으므로 성능도 제일 좋다. 상황에 맞추어 메서드를 사용하자!
Grouping (분류 함수)
개인적으로 제일 좋아하는 Collectors의 기능! 사용법 : collect(groupingBy(Student::getClass))
enum으로 그루핑하고 싶다면 다음 방법도 가능하다.
public enum Grade {
A(score -> score >= 95),
B(score -> score >= 90),
C(score -> score >= 80),
D(score -> score >= 70),
F(score -> score < 70);
private final Predicate<Integer> predicate;
Grade(Predicate<Integer> predicate) {
this.predicate = predicate;
}
public static Grade getGradeByScore(int score) {
return Arrays.stream(values())
.filter(value -> value.execute(score))
.findFirst()
.orElseThrow(() -> new IllegalStateException("wrong input"));
}
private boolean execute(int score) {
return predicate.test(score);
}
}
public static void main(String[] args) {
Map<Grade, List<Student>> collect = students.stream()
.collect(Collectors.groupingBy(student -> Grade.getGradeByScore(student.getScore())));
}
그렇다면 두 가지 이상의 기준을 동시에 적용할 수 있을까?
다수준 그룹화
Collectors.groupingBy
는 일반적으로 분류 함수와 컬렉터를 인수로 받는다. 즉 컬렉터 인수에 다시 한 번 grouping
을 사용한다면 다수준 그룹화가 가능하다.
서브그룹으로 데이터 수집
Collectors.groupingBy
의 두 번째 인수는 컬렉터를 받는다 했다. 그러면 각 인원의 수를 넣으려면 어떻게 할까? 다음과 같다.
Map<Grade, Long> collect1 = students.stream()
.collect(Collectors.groupingBy(student -> Grade.getGradeByScore(student.getScore()), Collectors.counting()));
그렇다면 가장 스코어가 높은 사람의 이름을 넣고 싶다면 어떻게 할까?
students.stream()
.collect(Collectors.groupingBy(student -> Grade.getGradeByScore(student.getScore()),
Collectors.maxBy(Comparator.comparingInt(Student::getScore))));
위와 같이 하면 Optional로 값이 다 감싸져 있을 것이다. 변환 함수도 같이 넣어줘야 하는데 그럴 때는 collectingAndThen()
함수를 사용하면 된다. 다음은 Optional까지 변환한 코드이다.
Map<Grade, String> collect2 = students.stream()
.collect(Collectors.groupingBy(student -> Grade.getGradeByScore(student.getScore()),
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Student::getScore)), opt -> opt.get().getName())));
이외에도 두 번째 인수에는 mapping도 가능하다.
partition(분할)
분할 함수는 Collectors.partitioningBy()
함수를 사용한다. boolean값으로만 키가 나눠지고 결국 true or false 의 키만 가질 수 있다는 것이다. 사용 방법은 그루핑 하는 방법과 같다.
Collectors 사용
정적 메소드 사용 : Collectors.toList(), Collectors.toMap(), Collectors.toSet(), Collectors.toConcurrentMap(), Collectors.toCollection(Supplier
-
collect custom 방법
stream() .collect( () -> new Container(), (r,t) -> r.accumulate(t), //r= 컨테이너, t=각 요소 (r1, r2) -> r1.combine(r2)); //마지막은 combine, 없어도 됨 싱글스레드는
-
Collectors.groupingBy() 도 사용가능 (groupingByConcurrent()는 스레드 세이프한 맵)
stream() .collect( Collectors.groupingBy( Student::getCity, //키가 될 값 Collectors.mapping(Student::getName, Collectors.toList()) //첫 번째 매개변수는 어떤 걸 value값 선정할 건지, 두 번째 매개변수는 어떤 방식으로 저장할 건지 ) )
-
그루핑 후 또 집계할 수 있게 두 번째 매개값으로 다음과 같은 Collector사용이 가능하다.
- Collectors.mapping(Function, Collector) : 매핑
- Collectors.averagingDouble(ToDoubleFuncton) : 평균값
- Collectors.counting() : 요소수
- Collectors.joining(…) : 문자 요소들을 연결
- Collectors.maxBy(Comparator) : 최대값
- Collectors.minBy(Comparator) : 최소값
- Collectors.reducing(…) : 커스텀 리덕션 값
- Collectors.summarrizingXXX(ToXXXFunction) : XXX 타입의 합계
**IntStream같은 경우 기본값이라 한 번 boxing이 필요하다.
Stream.generate()
팁 → 복권 번호 제조!
Stream.generate(() -> random.nextInt(45)+1)
.distinct()
.limit(6)
.collect(Collectors.toList());
ㅣ