이번 내용은 스트림에 대한 심화 내용이라고 할 수 있기 때문에 기본적인 내용은 아래 글을 참고하는게 좋다.
[Java] 스트림(Stream) - 특징, 파이프라인, 메서드 총정리
[Java] 스트림(Stream) - 특징, 파이프라인, 메서드 총정리
지금까지 많은 수의 데이터를 다룰 때, 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 for문과 Iterator를 이용해서 코드를 작성했다. 그러나 이런 방식으로 작성된 코드는 너무 길고
ta-mi.tistory.com
collect()는 스트림의 요소를 수집하는 최종 연산으로 리듀싱(reducing)과 유사하다. collect()가 스트림의 요소를 수집하려면, 어떻게 수집할 것인가에 대한 방법이 정의되어 있어야 하는데, 이 방법을 정의한 것이 바로 컬렉터(collector)이다. 컬렉터는 Collector 인터페이스를 구현한 것으로, 직접 구현할 수도 있고 미리 작성된 것을 사용할 수도 있다. Collectors 클래스는 미리 작성된 다양한 종류의 컬렉터를 반환하는 static 메서드를 가지고 있다.
collect() 스트림의 최종 연산, 매개변수로 컬렉터를 필요로 한다.
Collector 인터페이스로 컬렉터는 이 인터페이스를 구현해야 한다.
Collectors 클래스, static 메서드로 미리 작성된 컬렉터를 제공한다.
collect()
collect()는 매개변수의 타입이 Collector인데, 매개변수가 Collector를 구현한 클래스의 객체이어야 한다는 뜻이다. 그리고 collect()는 이 객체에 구현된 방법으로 스트림의 요소를 수집한다.
collect()의 인자
- 스트림을 컬렉션과 배열로 변환 : toList(), toSet(), toMap(), toCollection(), toArray()
스트림의 모든 요소를 컬렉션에 담아준다. List나 Set이 아닌 특정 컬렉션을 지정하려면 toCollection()에 해당 컬렉션의 생성자 참조를 매개변수로 넣어주면 된다.
List<String> names = stuStream.map(Student::getName).collect(Collectors.toList());
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = personStream.collect(Collectors.toMap(p->p.getRegId(), p->p));
Map은 키와 값의 쌍으로 저장해야하므로 객체의 어떤 필드를 키로 사용할지와 값으로 사용할지를 지정해줘야 한다.
스트림에 저장된 요소들을 배열로 변환하려면 toArray()를 사용하면 된다. 해당 타입의 생성자 참조를 매개변수로 지정해주면 된다. 만일 매개변수를 지정하지 않으면 반환 타입은 Object[]이다.
Student[] stuNames = studentStream.toArray(Student[]::new);
Object[] stuNames = studentStream.toArray();
Student[] stuNames = studentStream.toArray(); //에러
- 통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy()
스트림에서 최종 연산들이 제공하는 통계 정보를 collect()로 똑같이 얻을 수 있다. 곧 나올 groupingBy()와 함께 사용할 때 유용하게 사용된다.
long count = stuStream.count();
long count = stuStream.collect(Collectors.counting())
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore - strStream.collect(Collectors.summingInt(Student::getTotalScore)));
OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max();
Optional<Student> topStudent = stuStream
.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Student> topStudent topStudent = stuStream
.collect(maxBy(Coparaotr.comparingInt(Student::getTotalScore)));
IntSummaryStatistics stat = stuStream.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream.collect(summarizingInt(Student::getTotalScore));
- 리듀싱 - reducing()
IntStream에는 매개변수 3개짜리 collect()만 정의되어 있으므로 boxed()를 통해 IntStream을 Stream<Integer>로 변환해야 매개변수 1개짜리 collect()를 쓸 수 있다.
IntStream intStream = new Fandom().ints(1, 46).distinct().limit(6);
OptionalInt nax = intStream.reduce(Integer::max);
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max));
long sum = intStream.reduce(0, (a,b) -> a + b)
long sum = intStream.boxed().collect(reducing(0, (a,b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));
Collectors.reducing()에는 아래와 같이 3가지 종류가 있다. 세 번째 메서드만 제외하고는 reduce()와 같다. 세 번째 것은 위의 예에서 알 수 있듯이 map()과 reduce()를 하나로 합쳐놓은 것이다. 아래 코드는 와일드카드가 제거된 것이다.
Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op)
- 문자열 결합 - joining()
문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환한다. 구분자를 지정해줄 수 있고, 접두사와 접미사도 지정 가능하다. 스트림의 요소가 String이나 StringBuffer처럼 CharSequence의 자손인 경우에만 결합이 가능하므로 스트림의 요소가 문자열이 아닌 경우에는 먼저 map()을 이용해서 스트림의 요소를 문자열로 변환해야 한다.
만일 map() 없이 스트림에 바로 joining()하면, 스트림의 요소에 toString()을 호출한 결과를 결합한다.
String studentNames = stuStream.map(Student::getName).collect(joining());
String studentNames = stuStream.map(Student::getName).collect(joining(","));
String studentNames = stuStream.map(Student::getName).collect(joining(",","[","]"));
- 그룹화와 분할 - groupingBy(), partitioningBy()
그룹화는 스트림의 요소를 특정 기준으로 그룹화하는 것을 의미하고, 분할은 스트림의 요소를 두 가지, 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분할을 의미한다.
스트림을 두 개의 그룹으로 나눠야 한다면 partitioningBy()로 분할하는 것이 더 빠르고, 그 외에는 groupingBy()를 사용하면 된다. 둘 다 결과는 Map에 담겨 반환된다.
groupingBy()는 스트림의 요소를 Function(매개변수 타입)으로, partitioningBy()는 Predicate(매개변수 타입)로 분류한다.
- partitioningBy()에 의한 분류 예제
Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale));
//isMale()은 남학생, 여학생 구분 메서드
List<Student> maleStudent = stuBySex.get(true); //Map에서 남학생 목록을 얻음
List<Student> femaleStudent = stuBySex.get(false); //Map에서 여학생 목록을 얻음
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting()));
//남학생 수와 여학생 수 구하기
System.out.println("남학생 수 : " + stuNumBySex.get(true));
System.out.println("여학생 수 : " + stuNumBySex.get(false));
Map<Boolean, Optional<Student>> topScoreBySex = stuStream
.collect(partitioningBy(Student::isMale), maxBy(comparing(Student::getScore))));
Map<Boolean, Student> topScoreBySex = stuStream
.collect(partitioningBy(Student::isMale,
collectingAndThen(maxBy(comparingInt(Student::getScore)), Optional::get)));
maxBy()는 반환 타입이 Optional<Student>이다. 그냥 Student를 반환 결과로 얻으려면 아래 코드처럼 collectingAndThen()과 Optional::get을 함께 사용하면 된다.
- groupingBy()에 의한 분류 예제
groupingBy()로 그룹화를 하면 기본적으로 List<T>에 담는다. 원한다면 아래 두 번째 코드처럼 원하는 컬렉션으로 변환이 가능하다. 그 대신 Map의 지네릭 타입도 적절히 변경해야 한다는 것을 잊으면 안 된다.
여러 번 사용한다면 다수준 그룹화도 가능하다.
Map<Integer, List<Student>> stuByBan = stdStream.collect(groupingBy(Student::getBan));
Map<Integer, HashSet<Student>> stuByHakAndBan = stuStream
.collect(groupingBy(Student::getHak, toCollection(HashSet::new)));
자바의 정석(남궁성)을 정리한 내용입니다.
'Java' 카테고리의 다른 글
[Java] Collector 구현하기 (0) | 2022.09.18 |
---|---|
[Java] 스트림(Stream) - 특징, 파이프라인, 메서드 총정리 (1) | 2022.09.16 |
[Java] Optional<T>와 Optional 클래스 메서드 (2) | 2022.09.16 |
[Java] 함수형 인터페이스(Functional Interface) - 매개변수, 형 변환, 변수 참조, function 패키지 (0) | 2022.09.15 |
[Java] 람다식(Lambda expression) - 생성 규칙 / 메서드 참조 (0) | 2022.09.15 |