자바 스터디

[자바의정석] CH14 2.6~2.8

changha. 2023. 8. 5. 21:11

1. collect()와 Collectors()

collect()는 스트림의 요소들을 컬렉션으로 변환하거나, 요소들을 그룹화, 집계, 문자열로 결합하는 등의 작업을 수행하는데 사용 됨

 

‣ collect()는 Collector를 매개변수로 하는 스트림의 최종연산 (map은 중간연산)

Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) // 잘 안쓰임

 

‣ Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스 

public interface Collector<T, A, R> {
	Supplier<A> supplier(); // StringBuilder::new
    BiConsume<A, T> accumulator() // (sb, s) -> sb.append(s)
    BinaryOperator<A>  combiner(); // (sb1, sb2) -> sb1.append(sb2) 결합방법 (병렬)
    Function<A, R> finisher() // sb -> sb.toString()
    Set<Characteristics> characteristics(); // 컬렉터의 특성이 담긴 Set을 반환 
    
}

‣ Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공 

List<String> fruitsList = fruitsStream.collect(Collectors.toList());

 

collect는 최종연산, Collector는 인터페이스, Collectors는 collect를 구현한 구현체를 제공 

 

 

2. 스트림을 컬렉션, 배열로 변환 

 

‣ 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()

List<String> names = stuStream.map(Student::getName).collect(Collectors.toList()); // Stream<Student>->Stream<String>
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new)); // Stream<String> -> ArrayList<String>
Map<String, Person> map = personStream.collect(Collectors.toMap (p -> p.getRegId(). p -> p)); // Stream<Person> -> Map<String, Person>

‣ 스트림을 배열로 변환 - toArray()

Student[] stuNames = studentStream.toArray(Student[]::new); // OK
Student[] stuNames = studentStream.toArray(); // 에러 
Object[] stuNames = studentStream.toArray(); // OK

- 배열과 컬렉션 차이점 

• 배열: 배열은 한 번 생성되면 크기를 변경할 수 없다. 즉, 배열의 크기는 고정되어 있으며 요소를 추가하거나 삭제할 수 없다.

• 컬렉션: 컬렉션은 동적으로 크기가 변경될 수 있다. 요소를 추가하거나 삭제하여 컬렉션의 크기를 유동적으로 조절할 수 있다.

3. 스트림의 통계 - counting(), summingInt()

‣ 스트림의 통계정보 제공 - counting(), summingInt(), maxBy(), minBy(), ...

long count = stuStream.count();
long count = stuStream.collect(counting()); // Collectors.counting(), collect는 그룹별로 나눌 때 유용

Ex)

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Student> students = List.of(
            new Student("Alice", Gender.FEMALE, 85),
            new Student("Bob", Gender.MALE, 70),
            new Student("Charlie", Gender.MALE, 90),
            new Student("David", Gender.MALE, 80),
            new Student("Eva", Gender.FEMALE, 95)
        );

        // 성별(Gender)에 따라 그룹화하여 성적(grade)을 리스트로 모음
        Map<Gender, List<Integer>> gradesByGender = students.stream()
            .collect(Collectors.groupingBy(Student::getGender,
                Collectors.mapping(Student::getGrade, Collectors.toList())));

        // 결과 출력
        gradesByGender.forEach((gender, grades) -> {
            System.out.println(gender + " students' grades: " + grades);
        });
    }
}

enum Gender {
    MALE, FEMALE
}

class Student {
    private String name;
    private Gender gender;
    private int grade;

    public Student(String name, Gender gender, int grade) {
        this.name = name;
        this.gender = gender;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public Gender getGender() {
        return gender;
    }

    public int getGrade() {
        return grade;
    }
}

4. 스트림을 리듀싱 - reducing()

 스트림의 요소들을 감소시키는데 유용, 여러 연산을 하나로 축소하거나, 합계, 곱셈, 최댓값, 최솟값 등의 작업 가능 

 

‣ 스트림을 리듀싱 - reducing()

Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
  • BinaryOperator<T>:  같은 타입 T의 두 인수를 받아서 연산을 수행하고 결과를 반환하는 함수
  • T identity:  스트림이 비어있을 때 반환될 초기값입니다.

 

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        // Collectors.reducing을 사용하여 정수들의 합 구하기
        Optional<Integer> sumOptional = numbers.stream()
                .collect(Collectors.reducing((a, b) -> a + b));

        int sum = sumOptional.orElse(0); // 기본값 0으로 설정

        System.out.println("Sum: " + sum);
    }
}

‣ 문자열 스트림의 요소를 모두 연결 - joining()

String studentNames = stuStream.map(Student::getName).collect(joining));
String studentNames = stuStream.map(Student::getName).collect(joining(","))); // 구분자 
String studentNames = stuStream.map(Student::getName).collect(joining(",", "[", "]")); // [김자바, 이자바, ..]

5. 스트림의 그룹화와 분할 

‣ partitioningBy()는 스트림을 2분할한다.

Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)

• Predicate predicate :  스트림의 요소를 받아서 true 또는 false를 반환하는 함수로, 조건에 따라 요소가 참인 그룹과 거짓인 그룹으로 나누는 데 사용 된다. 

• Collector downstream :  두 그룹에 대해 적용할 추가적인 컬렉터이다. 이 컬렉터를 사용하여 각 그룹의 요소들을 더 세분화하거나 통계를 구할 수 있다.

Map<Boolean, List<Student>> stuByGender = stuStream.collect(partitioningBy(Student::isMale)); // 학생들을 성별로 분할 
List<Student> maleStudent = stuByGender.get(true); // 남학생 목록 
List<Sutdent> femaleStudent = stuByGender.get(false); // 여학생 목록
Map<Boolean, Long> stuNumByGender = stuStream.collect(partitioningBy(Student::isMale, counting())); // 분할 + 통계

‣ groupingBy()는 스트림을 n분할한다.

Map<Integer, List<Student>> stuByBan = stuStream.collect(groupingBy(Student::getBan, toList()));

학생들을 반별로 나눌 때 쓰임 

Map<Integer, Map<Integer, List<Student>>> stuByHakANdBan = stuStream.collect(groupingBy(Student::getHak, groupingBy(Student::getBan)))

학년, 반별로 나눌 때 Map안에 Map을 넣는 방식으로 가능 

 

6. 스트림의 변환 표