자바 기본 - 스트림

Posted by : at

Category : java-basic


스트림이란?

  • 컬렉션(배열 포함)의 요소를 하나씩 참조해서 람다식으로 처리할 수 있는 반복자.
  • 병렬처리도 쉽게 가능하기 때문에(내부 반복자 이용) 편리하다.
    • 병렬처리는 한가지 작업을 서브 작업으로 나눠서 분리되어서 처리하는 방식. (parallel만 붙이면 된다.)
  • 오리지날 스트림 → (필터링 처리) 중간 스트림 → (매핑 처리) 중간 스트림 → 집계 처리 결과물

스트림 종류

Reduction

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());


About Dani
Dani

많은 것을 배우고 싶은 사람

Email : bomdani9302@gmail.com

Website : https://qhals321.github.io

About 다니

많은 것을 배우고 나누고싶은 사람

Useful Links