[Stream] 수집, collect() 메소드, Collectors
- 프로그래밍 언어/Java
- 2019. 10. 11. 18:49
수집
- 요소들을 필터링/매핑한 후 요소들을 수집하는 최종처리 메소드를 제공
- 필요한 요소만 컬렉션으로 담을 수 있고, 요소들을 그룹핑한 후 집계할 수 있음
예제를 위한 Student 클래스
public class Student implements Comparable<Student>{
public enum Sex {MALE, FEMALE}
public enum City {Seoul, Pusan}
private String name;
private int score;
private Sex sex;
private City city;
public Student(String name, int score) {
super();
this.name = name;
this.score = score;
}
public Student(String name, int score, Sex sex) {
super();
this.name = name;
this.score = score;
this.sex = sex;
}
public Student(String name, int score, Sex sex, City city) {
super();
this.name = name;
this.score = score;
this.sex = sex;
this.city = city;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Sex getSex() {
return sex;
}
public City getCity() {
return city;
}
@Override
public String toString() {
return "Student [name=" + name + ", score=" + score + ", sex=" + sex + ", city=" + city + "]";
}
@Override
public int compareTo(Student o) {
return Integer.compare(score,o.score);
}
}
필터링한 요소 수집
- collect(Collector<T, A, R> collector)
- 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고 이를 리턴한다.
- T 요소를 A 누적기가 R에 저장한다는 의미
public class ToListExample {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("타곤",10, Student.Sex.MALE),
new Student("태알하",20, Student.Sex.FEMALE),
new Student("은섬",30, Student.Sex.MALE),
new Student("탄야",40, Student.Sex.FEMALE)
);
//남학생들만 묶어서 List 생성
List<Student> maleList = totalList.stream()
.filter(s -> s.getSex()==Student.Sex.MALE)
.collect(Collectors.toList());
maleList.stream().forEach(s -> System.out.println(s.getName()));
//타곤
//은섬
System.out.println();
//여학생들만 묶어서 HashSet 생성
Set<Student> femaleSet = totalList.stream().filter(s->s.getSex() == Student.Sex.FEMALE)
.collect(Collectors.toSet());
//.collect(Collectors.toCollection(HashSet::new)); 이런 형태도 가능
femaleSet.stream().forEach(s -> System.out.println(s.getName()));
//탄야
//태알하
}
}
- 다시한번 정리하자면 collect 메소드로 스트림을 묶어서 수집기인 Collector 객체의 메소드를 활용해서 특정 타입(List, Map, ConcurrentMap, Set)으로 반환할 수 있다.
- 여기서 ConcurrentMap은 멀티스레드 환경에서 사용하는 Map 계열이다. 스레드에 안전하다. 사용법은 기본 Map과 비슷하다.
사용자 정의 컨테이너에 수집
- List, Map, Set 같은 객체가 아닌 사용자가 직접 정의한 컨테이너 객체에 수집하는 방법.
- 직접 클래스로 만든다.
사용자 정의 컨테이너(남학생 클래스)
public class MaleStudent {
private List<Student> list;
public MaleStudent() {
list = new ArrayList<Student>();
System.out.println("["+Thread.currentThread().getName()+"] MaleStudent()");
}
public void accumulate(Student student) { //요소를 수집하는 메소드
list.add(student);
System.out.println("["+Thread.currentThread().getName()+"] accumulate()");
}
public void combine(MaleStudent other) { //두 MaleStudent를 결합하는 메소드(병렬 처리시에만 사용)
list.addAll(other.getList());
System.out.println("["+Thread.currentThread().getName()+"] combine()");
}
public List<Student> getList(){ //요소가 저장된 컬렉션을 리턴
return this.list;
}
@Override
public String toString() {
return "MaleStudent [list=" + list + ", getList()=" + getList() + ", getClass()=" + getClass() + ", hashCode()="
+ hashCode() + ", toString()=" + super.toString() + "]";
}
}
- 위 MaleStudent는 list 변수에 Student객체들을 담을 것임.(남학생들만)
- accumulate 메소드는 매개변수로 들어온 Student 객체를 list에 저장.
- combine 메소드는 다른 MaleStudent 객체가 들어오면 list에 합치는 과정.
Stream<Student> totalStream = totalList.stream(); //컬렉션을 스트림으로
//남자인 학생들의 스트림 생성.
Stream<Student> maleStream = totalStream.filter(s -> s.getSex() == Student.Sex.MALE);
Suplier<MaleStudent> supplier = () -> new MaleStudent();
BiConsumer<MaleStudent, Student> accumulator = (ms, s) -> ms.accumulate(s);
BiConsumer<MaleStudent, MaleStudent> combiner = (ms1, ms2) -> ms1.combine(ms2);
MaleStudent maleStudent = maleStream.collect(supplier, accumulator, combiner);
>> MaleStudent maleStudent = totalList.stream().filter(s -> s.getSex() == Student.Sex.MALE).collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine);
으로 메소드 참조 형태로 표현해서 사용할 수도 있다.
요소를 그룹핑해서 수집
- collect 메소드는 요소수집 기능 외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공.
- collect를 호출할 때, Collectors의 groupingBy() 또는 groupingByConcurrent()가 리턴하는 Collector를 매개값으로 대입하면 된다.
- 전자는 스레드에 안전하지 않은 Map을 후자는 스레드에 안전한 concurrentMap을 생성한다.
public class GroupingByExample {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("타곤",10, Student.Sex.MALE, Student.City.Seoul),
new Student("태알하",20, Student.Sex.FEMALE, Student.City.Pusan),
new Student("은섬",30, Student.Sex.MALE, Student.City.Pusan),
new Student("탄야",40, Student.Sex.FEMALE, Student.City.Seoul)
);
Map<Student.Sex, List<Student>> mapBySex = totalList.stream()
.collect(Collectors.groupingBy(Student::getSex));
System.out.println("[남학생] ");
mapBySex.get(Student.Sex.MALE).stream().forEach(s->System.out.print(s.getName()+" "));
System.out.println("\n[여학생] ");
mapBySex.get(Student.Sex.FEMALE).stream().forEach(s-> System.out.print(s.getName()+" "));
System.out.println();
Map<Student.City, List<String>> mapByCity = totalList.stream()
.collect(
Collectors.groupingBy(
Student::getCity, //객체에서 그룹핑할 요소
TreeMap::new,
Collectors.mapping(Student::getName, Collectors.toList())
//City로 그룹핑하였을 때 Map에 value로 가지는 List의 String으로 들어갈 값., 콜렉터
)
);
System.out.println("\n[서울] ");
mapByCity.get(Student.City.Seoul).stream().forEach(s->System.out.print(s+" "));
System.out.println("\n[부산] ");
mapByCity.get(Student.City.Pusan).stream().forEach(s->System.out.print(s+" "));
}
}
결과
[남학생] 타곤 은섬 [여학생] 태알하 탄야 [서울] 타곤 탄야 [부산] 태알하 은섬 |
그룹핑 후 매핑 및 집계
- Collectors.groupingBy 메소드는 그룹핑 후, 매핑이나 집계(평균, 카운팅, 연결, 최대, 최소, 합계)를 집계할 수 있도록 두번째 매개값으로 Collector를 가질 수 있다.
예제 : 학생들을 성별로 그룹핑한 후, 점수를 평균내고 성별별로 이름을 반환한다.
public class GroupingAndReduction {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("타곤",10, Student.Sex.MALE),
new Student("태알하",20, Student.Sex.FEMALE),
new Student("은섬",30, Student.Sex.MALE),
new Student("탄야",40, Student.Sex.FEMALE)
);
//성별로 점수를 저장하는 Map 얻기
Map<Sex, Double> mapBySex = totalList.stream()
.collect(
Collectors.groupingBy(
Student::getSex, //성별별로
Collectors.averagingDouble(Student::getScore) //평균 집계내기
)
);
System.out.println("남학생 평균 점수 : "+mapBySex.get(Student.Sex.MALE));
System.out.println("여학생 평균 점수 : "+mapBySex.get(Student.Sex.FEMALE));
//성별을 쉼표로 구분한 이름을 저장하는 Map 얻기
Map<Student.Sex, String> mapByName = totalList.stream()
.collect(
Collectors.groupingBy(
Student::getSex,
Collectors.mapping(Student::getName, Collectors.joining(","))
//"," 로 조인하여 요소들 구분하기.
)
);
System.out.println("남학생 전체 이름 : "+mapByName.get(Student.Sex.MALE));
System.out.println("여학생 전체 이름 : "+mapByName.get(Student.Sex.FEMALE));
}
}
'프로그래밍 언어 > Java' 카테고리의 다른 글
[람다식] Java 람다식 멤버변수, 로컬변수 사용하기(this 사용) (0) | 2019.10.27 |
---|---|
[람다식] Java 함수형 인터페이스 @FunctionalInterface (0) | 2019.10.26 |
[Stream] 집계, Optional 클래스, 커스텀 집계(reduce) (0) | 2019.09.19 |
[Java 알고리즘] x만큼 간격이 있는 n개의 숫자, 프로그래머스 level1 (0) | 2019.09.18 |
[Stream] 정렬(sort), 루핑(peek, forEach, 매칭(allMatch, anyMatch, noneMatch) (0) | 2019.09.18 |