본문 바로가기
자바

[Java] 자바의 스트림(Stream)

by 책 읽는 개발자_테드 2021. 9. 9.
반응형

학습목표

 · 스트림이란?

 · 스트림 사용방법

 · 스트림의 특징

 · 스트림의 종류

 · 스트림 구현 객체를 얻기 위한 메소드

   - 컬렉션에서 스트림 얻기

   - 배열에서 스트림 얻기

   - 숫자 범위에서 스트림 얻기

   - 파일에서 스트림 얻기

   - 디렉토리에서 스트림 얻기

 · 스트림 파이프라인

 · 중간 처리 메소드와 최종 처리 메소드 종류

   - 필터링: distinct(), filter()

   - 매핑: flatXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()

   - 정렬: sorted() 

   - 루핑: peek(), forEach()

   - 매칭: allMatch, anyMatch, noneMatch()

   - 기본 집계: sum(), count(), average(), max(), min()

   - 커스텀 집계: reduce()

   - 수집: collect()


 

스트림이란?

· 배열과 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자

· 람다식: 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수

 

스트림 사용방법

· 자바 8 이후 스트림을 통한 배열, 컬렉션의 순차적 처리: 람다식 이용

// java.util.Collection의 stream() 메소드로 스트림 객체를 얻고, 
// stream.forEach 메소드를 통해 컬렉션 요소를 하나씩 콘솔에 출력
List<String> list = Arrays.asList("tester1","tester2","tester3");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));

· 자바 7 이전의 배열, 컬렉션의 순차적 처리: Iterator 반복자 이용

List<String> list = Arrays.asList("tester1","tester2","tester3");
Iterator<String> iterator = list.iterator();

while(iterator.hasNext()){
   String name = iterator.next();
   System.out.println(name);
}

 

스트림의 특징

· 람다식으로 요소 처리 코드를 제공

· Stream이 제공하는 대부분의 요소 처리 메소드는 함수형 인터페이스 매개 타입을 가지므로, 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 매개값으로 전달

public class LamdaExpressionExam {

   public static void main(String[] args){

       List<String> vegetables = Arrays.asList("broccoli","tomato","cabbage");
       Stream<String> stream = vegetables.stream();
       stream.forEach(s -> {
           System.out.println(s);
       });
   }
}

· 내부 반복자를 사용하므로 병렬 처리가 쉬움

· 내부 반복자(internal iterator): 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공

· 외부 반복자(external iterator): 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져옴

ex) index를 이용하는 for문, Iterator를 이용하는 while문

https://cornswrold.tistory.com/293

 

· 내부 반복자의 장점: 

1. 반복은 컬렉션에 맡겨두고, 요소 처리 코드에만 집중할 수 있음 (할 일이 줄어듦)

2. 코드가 간결해짐

3. 요소의 병렬 처리가 컬렉션 내부에서 처리

 

· 병렬 처리: 한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것

https://cornswrold.tistory.com/293

예시 - 병렬 처리와 순차 처리 비교

public class ParallelExam {
   public static void main(String args[]){
       List<String> vegetables = Arrays.asList("broccoli","tomato","cabbage","eggplant");

       //순차 처리
       Stream<String> stream = vegetables.stream();
       stream.forEach(ParallelExam :: print); //메서드참조

       //병렬 처리
       Stream<String> parallelStream = vegetables.parallelStream();
       parallelStream.forEach(ParallelExam::print);
   }

   public static void print(String str){
       System.out.println(str+ " : " + Thread.currentThread().getName());
   }
}

결과

· 중간 처리와 최종 처리를 할 수 있음

     - 중간 처리: 매핑, 필터링, 정렬

     - 최종 처리: 반복, 카운팅, 평균, 총합 등 집계 처리

 

https://url.kr/usdqye

 

 예시 - 야채 무게를 가져와서(중간 처리) 평균값을 산출(최종 처리)

public class MapAndReduceExam {
   public static void main(String args[]){

       List<Vegetable> vegetables = Arrays.asList(
               new Vegetable("broccoli",0.3),
               new Vegetable("tomato",0.2),
               new Vegetable("cabbage",2.0),
               new Vegetable("eggplant",0.5)
       );

       //순차 처리
       Stream<Vegetable> stream = vegetables.stream();
       double average = stream
               .mapToDouble(Vegetable :: getKg) //중간처리: 야채 객체를 무게로 매핑
               .average() //최종 처리(평균 점수)
               .getAsDouble();
       System.out.println(average);
   }

   static class Vegetable{
       String name;
       double kg;

       Vegetable(String name, double kg){
           this.name = name;
           this.kg = kg;
       }
       public String getName() {
           return name;
       }
       public double getKg() {
           return kg;
       }
   }
}

결과

 

스트림의 종류

· java.util.stream 패키지에는 스트림 API들이 존재하며, BaseStream 인터페이스를 부모로 다양한 자식 인터페이스가 존재함

https://namocom.tistory.com/778

· BaseStream: 모든 스트림에서 사용할 수 있는 공통 메소드 정의

· Stream: 객체 요소를 처리하는 스트림

· InteStream, LongStream, DoubleStream: int, long, double 기본 타입 요소를 처리하는 스트림

 

스트림 구현 객체를 얻기 위한 메소드

리턴 타입 메소드(매개 변수) 소스
Stream<T> java.util.Collection.stream()
java.util.Collection.parallelStream()
컬렉션
Stream<T>
IntStream
LongStream
DoubleStream
Arrays.stream(T[]),             Stream.of(T[])
Arrays.stream(int []),          IntStream.of(int[])
Arrays.stream(long[]),        LongStream.of(long[])
Arrays.stream(double[]),    DoubleStream.of(double[])
배열
IntStream IntStream.range(int, int)
IntStream.rangeClosed(int, int)
int 범위
LongStream LongStream.range(long, long)
LongStream.rangeClose(long, long)
long 범위
Stream<Path> Files.find(Path, int, BiPredicate, FileVisitOption)
Files.list(Path)
디렉토리
Stream<String> Files.line(Path, charset)
BufferedReader.lines()
파일
DoubleStream
IntStream
LongStream
Random.doubles()
Random.ints()
Random.longs()
랜덤 수

 

컬렉션에서 스트림 얻기

List<String> list = Arrays.asList("tester1","tester2","tester3");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));

 

배열에서 스트림 얻기

String[] vegetables = {"broccoli","tomato","cabbage"};
Stream<String> stream = Arrays.stream(vegetables);
stream.forEach(s-> System.out.println(s))

 

숫자 범위에서 스트림 얻기

IntStream intStream = IntStream.rangeClosed(1,100);
intStream.forEach(i -> System.out.println(i)); //1부터 100까지 출력

 

파일에서 스트림 얻기

· Files와 BufferedReader의 lines() 메소드를 이용하여 문자 파일의 내용을 스트림을 통해 행 단위로 읽고 콘솔에 출력

Path path = Paths.get("/Users/ted.sc/Desktop/java8.txt");
Stream<String> stream;

//1. Filses.lines() 메소드 이용
stream = Files.lines(path, Charset.defaultCharset()); //운영체제의 기본 문자셋
stream.forEach(System.out::println); //메소드 참조, (s->System.out.println(s))과 동일

//2. BufferedReader의 lines() 메소드 이용
File file = path.toFile();
FileReader fileReader = new FileReader(file);
BufferedReader br = new BufferedReader(fileReader);
stream = br.lines();
stream.forEach(System.out::println);

 

결과

 

디렉토리에서 스트림 얻기

Path path = Paths.get("/Users/ted.sc/Desktop");
Stream<Path> stream = Files.list(path); //서브 디렉토리, 파일 목록을 스트림에 입력
stream.forEach(System.out::println);

 

스트림 파이프라인

 

· 파이프라인: 여러 개의 스트림이 연결되어 있는 구조

· 리덕션(Reduction): 대량의 데이터를 가공해서 축소하는 것 ex) 합계, 평균값 ,카운팅, 최대값, 최소값 

 

· 컬렉션의 요소를 리덕션의 결과물로 바로 집계 할 수 없을 경우, 집계하기 용이하도록 중간 처리(필터링,매핑 정렬, 그룹핑 등)가 필요

· 스트림은 중간 처리와 최종 처리(리덕션)를 파이프라인으로 처리

· 중간 스트림이 생성될 때 바로 중간 처리 되지 않고, 최종 처리가 시작되기 전까지 중간 처리는 지연(lazy)

· 최종 처리가 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 처리되어 최종 처리까지 오게됨 

· 파이프라인에서 최종처리를 제외하고는 모두 중간 처리 스트림

 

https://sas-study.tistory.com/239

 

 

· Stream 인터페이스에는 다양한 중간 처리 메소드(필터링, 매핑, 정렬 등)가 존재하고, 이 메소드들은 중간 처리된 스트림을 리턴함

· 리턴된 중간 처리된 스트림에서 다시 중간 처리 메소드를 호출하여 파이프라인 형성

 

 예시 - 회원 컬렉션에서 남자 필터링 중간 스트림 +  나이 매핑 중간 스트림 연결  +  평균 나이 집계 

https://sas-study.tistory.com/239

public class PipeLineExam {
   public static void main(String[] args){
       Member[] members = {new Member(20, "남"),
               new Member(20, "여"),
               new Member(30, "남"),
               new Member(30, "여"),
               new Member(40, "남")};

       //로컬 변수 생략x
       Stream<Member> memberStream = Arrays.stream(members);
       Stream<Member> maleStream = memberStream.filter(m->m.getSex().equals("남"));
       IntStream maleAgeStream = maleStream.mapToInt(Member::getAge);
       double ageAvg = maleAgeStream.average().getAsDouble();
       System.out.println("나이:"+ageAvg);

       //로컬 변수 생략o
       double ageAvg2 = Arrays.stream(members)		// 오리지널 스트림
               .filter(m->m.getSex().equals("남"))   	// 중간 처리 스트림
               .mapToInt(Member::getAge)		// 중간 처리 스트림
               .average()				// 중간 처리 스트림
               .getAsDouble();				// 최종 처리
       System.out.println("나이:"+ageAvg2);
   }

   static class Member{
       private int age;
       private String sex;
       public Member(int age, String sex) {
           this.age = age;
           this.sex = sex;
       }
       public int getAge() {
           return age;
       }
       public String getSex() {
           return sex;
       }
   }
}

 

중간 처리 메소드와 최종 처리 메소드 종류

https://steady-coding.tistory.com/313
https://steady-coding.tistory.com/314?category=861122

· 중간 처리 메소드와 최종 처리 메소드는 리턴 타입으로 쉽게 구분

   - 중간 처리 메소드의 리턴 타입: 스트림

   - 최종 처리 메소드의 리턴 타입: 기본 타입 또는 OptionalXXX 

· 위 표의 소속된 인터페이스의 공통의 의미는 Stream, IntStream, LongStream, DoubleStream에서 모두 제공된다는 뜻

 

필터링 

· 요소를 걸러내는 중간 처리 기능

· 모든 스트림이 가지고 있는 공통 메소드

· 스트림에서 제공하는 필터링 메소드: distinct(), filter()

리턴타임 메서드(매개변수)  설명 
Stream
IntStream
LongStream
DoubleStream
distinct() 중복 제거
filter(Predicate) 조건 필터링
filter(IntPredicate) 
filter(LongPredicate)
filter(DoublePredicate)
 

 

distinct() 

· 중복을 제거

· Stream의 경우 Object.equals(Object)가 true이면 중복 제거하고 IntStream, LongStream, DoubleStream의 경우 동일값이면 중복 제거

▶ 예시 - 중복된 야채 이름을 제거

List<String> vegetables = Arrays.asList("가지","무","배추","가지","가지","애호박");
vegetables.stream()
        .distinct()
        .forEach(System.out::println);

결과

filter() 

· 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링

예시 - 야채 이름에 ‘호박’이 포함된 것만 필터링

List<String> vegetables = Arrays.asList("가지","무","배추","단호박","늙은호박","애호박");
vegetables.stream()
       .distinct() // 중복제거
       .filter(s->s.contains("호박")) //필터링
       .forEach(System.out::println);

결과

 

매핑

· 스트림의 요소를 다른 요소로 대체하는 중간 처리 기능

· 스트림에서 제공하는 매핑 메소드: flatXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()

 

flatMapXXX()

· 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴

https://jang8584.tistory.com/237

 

리턴타입 메소드(매개변수) 요소 -> 대체 요소
Stream<R> flatMap(Function<T, Stream<R>>) T -> Stream<R>
DoubleStream flatMap(DoubleFunction<DoubleStream>) double -> DoubleStream
IntStream flatMap(IntFunction<IntStream>) int -> IntStream
LongStream flatMap(LongFunction<LongStream>) long -> longStream
DoubleStream flatMapToDouble(Function<T, DoubleStream>) T -> DoubleStream
IntStream flatMapToInt(Function<T, IntStream>) T -> IntStream
LongStream flatMapToLong(Function<T, LongStream>) T -> LongStream

 

 예시 - 리스트에 저장된 요소들을 나누어 새로운 스트림을 생성

//문장을 어절로 나누기
List<String> line = Arrays.asList("애호박 무침은 고소하다","노각 무침은 아삭하다");

line.stream()
   .flatMap(data -> Arrays.stream(data.split(" ")))
   .forEach(System.out::println);

//문장을 숫자로 나누기
List<String> line2 = Arrays.asList("10,20,30","40,50,60");

line2.stream()
    .flatMapToInt(data -> {
            String[] strArr = data.split(",");
            int[] numbers = new int[strArr.length];
            for(int i=0;i<strArr.length;i++)
                numbers[i] = Integer.parseInt(strArr[i]);
            return Arrays.stream(numbers);
    }).forEach(System.out::println);

결과

 

mapXXX()

· 요소를 대체하는 요소로 구성된 새로운 스트림 리턴

https://jang8584.tistory.com/237

리턴타입 메소드(매개변수) 요소 -> 대체 요소
Stream<R> map(Function<T,R>) T->R
DoubleStream mapToDouble(ToDoubleFunction<T>) T->double
IntStream mapToInt(ToIntFunction<T>) T->int
LongStream mapToLong(ToLongFunction) T->long
DoubleStream map(DoubleUnaryOperator) double->double
IntStream mapToInt(DoubleToIntFunction) double->int
LongStream mapToLong(DoubleTOLongFunction) double->long
Stream<U> mapToObj(DoubleFunction<U>) double->U
IntStream map(IntUnaryOperator mapper) int->int
DoubleStream mapToDouble(IntToDoubleFunction) int->double
LongStream mapToLong(IntToLongFunction mapper) int->long
Stream<U> mapToObj(IntFunction<U>) int->U
LongStream map(LongUnaryOperator) long->long
DoubleStream mapToDouble(LongToDoubleFunction) long->double
IntStream mapToInt(LongToIntFunction) long->int
Stream<U> mapToObj(LongFunction<U>) long->U

 예시 - 문장을 숫자로 나누기

public class MapDemo {
    public static void main(String[] args){
        List<String> line = Arrays.asList("10,20,30","40,50,60");

        line.stream()
                .flatMap(data-> {
                    String[] strArr = data.split(",");
                    return Arrays.stream(strArr);
                }) // 모든 문장을 ','을 기준으로 나눈 새로운 스트림 생성
                .mapToInt(Integer::parseInt) //스트림 각 요소를 Integer로 변경한 새로운 스트림 생성
                .forEach(System.out::println);
    }
}

 

예시 - 문자를 대문자로 변경

List<String> list =
        Arrays.asList("a1", "a2", "b1", "b2", "c2", "c1", "c3");
Stream<String> stream2 = list.stream();
stream2.map(s -> s.toUpperCase()).forEach(System.out::println);

 

asDoubleStream(), asLongStream(), boxed()

리턴타입 메소드(매개변수) 설명
DoubleStream asDoubleStream() int -> double, long -> double
LongStream asLongStream() int -> long
Stream<Integer>
Stream<Long>
Stream<Double>
boxed() int -> Integer
long -> Long
double -> Double

· asDoubleStream() 메소드는 IntStream의 int 요소 또는 LongStream의 long 요소를 double 요소로 타입 변환하여 DoubleStream 생성   

· asLongStream() 메소드IntStream의 int 요소를 long 요소로 타입 변환하여 LongStream 생성   

· boxed() 메소드는  int, long, double 요소를 Integer, Long, Double 요소로 박싱하여 Stream을 생성

 

 예시 - 다른 요소로 대체

public class AsDoubleStreamAndBoxedDemo {
    public static void main(String[] args){
        int[] intArray = {1,2,3,4,5};
        IntStream intStream = Arrays.stream(intArray);
        intStream.asDoubleStream() //DoubleStream생성
                .forEach(System.out::println);

        intStream = Arrays.stream(intArray);
        intStream.boxed() //Stream<Integer> 생성
                .forEach(obj-> System.out.println(obj.intValue()));
    }
}

결과

정렬 

· 요소가 최종 처리되기 전 중간 단계에서 요소를 정렬하여 최종 처리 순서를 변경하는 기능

· 스트림에서 제공하는 매핑 메소드: sorted()

 

객체 요소일 경우 클래스가 Comparable을 구현하지 않으면 sorted() 메소드를 호출했을 때 ClassCastException이 발생

리턴타입 메소드(매개변수) 설명
Stream<T> sorted() 객체를 Comarable 구현 방법에 따라 정렬
Stream<T> sorted(Comparator<T>) 객체를 주어진 Comparator에 따라 정렬
DoubleStream sorted() double 요소를 오름차순으로 정렬
IntStream sorted() int 요소를 오름차순으로 정렬
LongStream sorted() long 요소를 오름차순으로 정렬

 

 예시 - 야채 객체의 무게를 오름차순으로 정렬

public class SortedExam {
   public static void main(String[] args){
       List<Vegetable> vegetables = Arrays.asList(
               new Vegetable("broccoli",0.3),
               new Vegetable("tomato",0.2),
               new Vegetable("eggplant",0.5)
       );
       Stream<Vegetable> stream = vegetables.stream();
       //정렬
       stream.sorted().forEach(v -> System.out.println(v.name + ":" + v.kg));
   }

   static class Vegetable implements Comparable<Vegetable>{
       private String name;
       private double kg;

       Vegetable(String name, double kg){
           this.name = name;
           this.kg = kg;
       }

       @Override
       public int compareTo(Vegetable o) {
           return Double.compare(kg, o.kg);
           // score < o.score 음수 리턴
           // score == o.score 0 리턴
           // score > o.score 양수 리턴
       }
   }
}

결과

· 객체 요소가 Comparable을 구현한 상태에서 기본 비교(Comparable) 방법으로 정렬하고 싶다면 다음 중 하나를 호출

sorted();

sorted((a,b)->a.compareTo(b));

sorted(Comparator.naturalOrder());

stream.sorted().forEach(v -> System.out.println(v.name + ":" + v.kg));
stream.sorted((a,b)->a.compareTo(b)).forEach(v -> System.out.println(v.name + ":" + v.kg));
stream.sorted(Comparator.naturalOrder()).forEach(v -> System.out.println(v.name + ":" + v.kg));

 

· 객체 요소가 Comparable을 구현하지만, 정반대로 정렬하고 싶다면 다음 중 하나를 호출

sorted( (a,b) -> b.compareTo(a) );

sorted( Comparator.reverseOrder() );

vegetables.stream().sorted(Comparator.reverseOrder()).forEach(v -> System.out.println(v.name + ":" + v.kg));
vegetables.stream().sorted((a,b)->b.compareTo(a)).forEach(v -> System.out.println(v.name + ":" + v.kg));

 

· 객체 요소가 Comparable을 구현하지 않았다면 Comparator를 매개값으로 갖는 sorted() 메소드를 사용

     - Comparator는 함수형 인터페이스이므로 람다식으로 매개값 작성 가능

sorted((a,b) -> {})

public class SortedDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",0.3),
                new Vegetable("tomato",0.2),
                new Vegetable("eggplant",0.5)
        );

        //정렬
        vegetables.stream().sorted((a,b) -> {
            return a.kg.compareTo(b.kg);
        }).forEach(v -> System.out.println(v.name + ":" + v.kg));
    }

    static class Vegetable{
        private String name;
        private Double kg;

        Vegetable(String name, double kg){
            this.name = name;
            this.kg = kg;
        }
    }
}

 

 예시 - int 기본 자료형 배열을 정렬

//배열 생성
int[] intArr = {1,2,3,4,5};
//오름차순 정렬
Arrays.stream(intArr).sorted().forEach(System.out::println);
//내림차순 정렬, boxed()를 통해 IntStream -> Stream<Integer>로 형변환 필요
Arrays.stream(intArr).boxed().sorted(Comparator.reverseOrder()).forEach(System.out::println);

· 기본 자료형 스트림의 정렬 메서드는 sorted()이므로 내림차순(sorted(Comparator<T>) 활용)을 구현하려면 boxed() 메서드를 통해 XXXStream을 Stream<T>으로 형변환 필요

 

루핑 

· 요소 전체를 반복하는 기능

· 스트림에서 제공하는 매핑 메소드: peek(), forEach()

리턴타입 메소드(매개변수)
Stream<T> peek(Consumer<? super T> action)
void forEach(Consumer<? super T> action)

 

peek()

· 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용하는 중간 처리 메소드

· 최종 처리 메소드가 실행하지 않으면 지연되므로 반드시 최종 처리 메소드가 호출되어야 동작

forEach()

· 파이프라인 마지막에 루핑하며 요소를 하나씩 처리하는 최종 처리 메소드

 

예시 - 루핑을 통한 요소 출력

        int[] intArr = {1,2,3,4,5};

        //peek 동작하지 않음
        Arrays.stream(intArr)
                .filter(a->a%2==0)
                .peek(System.out::println);

        //peek 동작함
        int total = Arrays.stream(intArr)
                .filter(a->a%2==0)
                .peek(System.out::println)
                .sum();
        System.out.println("총합:"+total);

        //forEach
        Arrays.stream(intArr)
                .filter(a->a%2==0)
                .forEach(System.out::println);

결과

매칭 

· 요소들이 특정 조건에 만족하는지 조사하는 최종 처리 메소드

· 스트림에서 제공하는 매칭 메소드: allMatch, anyMatch, noneMatch()

 

allMatch() - 모든 요소들이 매개값으로 주어진 Predicate 조건을 만족하는지 조사

anyMatch() - 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사

noneMatch() - 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사

리턴타입 메소드(매개변수) 제공 인터페이스
boolean allMatch(Predicate predicate), anyMatch(Predicate predicate), noneMatch(Predicate predicate) Stream
boolean allMatch(IntPredicate predicate), anyMatch(IntPredicate predicate), noneMatch(IntPredicate predicate) IntStream
boolean allMatch(LongPredicate predicate), anyMatch(LongPredicate predicate), noneMatch(LongPredicate predicate) LongStream
boolean allMatch(DoublePredicate predicate), anyMatch(DoublePredicate predicate), noneMatch(DoublePredicate predicate) DoubleStream

 

예시 - 요소들의 배수 조사

int[] arr = {2,4,6};

boolean isTrue = Arrays.stream(arr)
       .allMatch(a->a%2==0);
System.out.println("모두 2의 배수인가? " + isTrue);

isTrue = Arrays.stream(arr)
       .anyMatch(a->a%3==0);
System.out.println("하나라도 3의 배수가 있는가? " + isTrue);

isTrue = Arrays.stream(arr)
       .noneMatch(a->a%3==0);
System.out.println("3의 배수가 없는가? " + isTrue);

결과

 

기본 집계

· 집계(Aggregate)는 요소를 처리해서 카운팅, 합계, 평균값, 최대값, 최소값과 같이 하나의 값으로 산출하는 것

· 최종 처리 메소드

· 대량의 데이터를 가공해서 출소하는 리덕션(Reduction)으로 볼 수 있음

· 스트림에서 제공하는 기본 집계 메소드: sum(), count(), average(), max(), min()

리턴타입 메소드(매개변수) 설명
long count() 요소 개수
OptionalXXX findFirst() 첫 번째 요소
Optional<T> max(Comparator<T>) 최대 요소
OptionalXXX max()
Optional<T> min(Comparator<T>) 최소 요소
OptionalXXX min()
OptionalDouble average() 요소 평균
int, long, double sum() 요소 총합

· Optional 클래스란? -> https://scshim.tistory.com/345

 

 예시 - 기본 집계 메소드 활용

        int[] intArray = new int[] {1,2,3,4,5};
        
        long count = Arrays.stream(intArray).count();
        long sum = Arrays.stream(intArray).sum();
        Double average = Arrays.stream(intArray).average().getAsDouble();
        long max = Arrays.stream(intArray).max().getAsInt();
        int first = Arrays.stream(intArray).findFirst().getAsInt();

        System.out.println("count:"+count);
        System.out.println("sum:"+sum);
        System.out.println("average:"+average);
        System.out.println("max:"+max);
        System.out.println("first:"+first);

결과

 

커스텀 집계 

· 스트림은 기본 집계 메소드 이외에 프로그램화해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드를 제공

· 최종 처리 메소드

인터페이스 리턴타입 메소드(매개 변수)
Stream Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
IntStream OptionalInt reduce(BinaryOperator op)
int reduce(int identity, IntBinaryOperator op)
LongStream OptionalLong reduce(LongBinaryOperator op)
long reduce(long identity, LongBinaryOperator op)
DoubleStream OptionalDouble reduce(DoubleBinaryOperator op)
double reduce(double identity, DoubleBinaryOperator op)

· 스트림 요소가 전혀 없을 경우 디폴트 값인 identity 매개값이 리턴

 

예시 - 야채 무게 더하기

public class ReduceDemo {
    public static void main(String[] args){
        List<Vegetable> vegetableList = Arrays.asList(
                new Vegetable("broccoli",0.3),
                new Vegetable("tomato",0.2),
                new Vegetable("cabbage",2.0),
                new Vegetable("eggplant",0.5)
        );
        // sum() 이용
        Double sum1 = vegetableList.stream()
                .mapToDouble(Vegetable::getKg)
                .sum();

        // reduce(DoubleBinaryOperator op) 이용
        Double sum2 = vegetableList.stream()
                .map(Vegetable::getKg)
                .reduce((a,b)->a+b) //스트림에 요소가 없을 경우 NoSuchElementException 발생
                .get();

        // reduce(double identity, DoubleBinaryOperator op) 이용
        Double sum3 = vegetableList.stream()
                .map(Vegetable::getKg)
                .reduce(0.0, (a,b)->a+b); //스트림에 요소가 없을 경우 디폴트 값(identity) 리턴

        System.out.println(sum1);
        System.out.println(sum2);
        System.out.println(sum3);
    }
    static class Vegetable{
        String name;
        double kg;

        Vegetable(String name, double kg){
            this.name = name;
            this.kg = kg;
        }
        public double getKg() {
            return kg;
        }
    }
}

결과

수집 

· 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드

· 필요한 요소만 컬렉션으로 담거나, 요소들을 그룹핑한 후 집계(리덕션)할 수 있음

· 스트림에서 제공하는 수집 메소드: collect()

 

https://starplatina.tistory.com/entry/Java-8-Stream-API-PART-2

 

· 예시 1) 스트림 collect()를 사용한 List to Map 

https://www.baeldung.com/java-list-to-map

 

· 예시2) 스트림을 사용하여 String 컬렉션을 하나의 String으로 연결하기 

List<String> vegetables = new ArrayList<>();
vegetables.add("cucumber");
vegetables.add("carrot");
vegetables.add("eggplant");
String vegetableLine = vegetables.stream()
   .collect(Collectors.joining(", "));
System.out.println(vegetableLine);

결과

필터링한 요소 수집

· Stream의 collect(Collector<T,A,R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하여 리턴함

리턴 타입 메소드(매개 변수) 인터페이스
R collect(Collector<T,A,R> collector) Stream

· 매개값인 Collector(수집기)는 어떤 요소를 어떤 컬렉션에 수집할 것인지 결정

· Collector의 타입 파타미터 의미: T - 요소, A - 누적기(accumulator) , R - 요소가 저장될 컬렉션

     - T 요소를 A 누적기가 R에 저장한다는 의미

· Collector의 구현 객체는 Collectors 클래스의 정적 메소드를 이용해 얻을 수 있음

리턴 타입 Collectors의 정적 메소드 설명
Collector<T,?,List<T>> toList() T를 List에 저장
Collector<T,?,Set<T>> toSet() T를 Set에 저장
Collector<T,?,Collection<T>> toCollectio(
Supplier<Collection<T>>)
T를 Supplier가 제공한 Collection에 저장
Collector<T,?,Map<K,U>> toMap(
Function<T,K> keyMapper,
Function<T,U> valueMapper)
T를 K와 U로 매핑해서 K를 키로 U를 Map에 저장
Collector<T,?,
ConcurrentMap<K,U>>
toConcurrentMap(
Function<T,K> keyMapper,
Function<T,U> valueMapper)
T를 K와 U로 매핑해서 K를 키로 U를 ConcurrentMap에 저장

· 리턴 타입의 Collector의 A가 ?로 되어 있는 이유: Collector가 R(컬렉션)에 T(요소)에를 저장하는 방법을 알고 있어 A(누적기)가 필요 없기 때문이다.

· ConcurrentMap은 스레드에 안전, Map은 스레드에 안전 x

     - 멀티 스레드 환경에서 ConcurrentMap 이용 권장

 

 예시 - 야채 색상별로 서로 다른 자료구조에 저장하기 

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN),
                new Vegetable("tomato", Vegetable.Color.RED),
                new Vegetable("redPepper",Vegetable.Color.RED),
                new Vegetable("cabbage",Vegetable.Color.GREEN),
                new Vegetable("eggplant",Vegetable.Color.PURPLE)
        );

        // 초록색 야채를 List 자료구조에 저장
        Stream<Vegetable> greenVegetableStream = vegetables.stream()
                .filter(s->s.getColor()==Vegetable.Color.GREEN);
        Collector<Vegetable, ?, List<Vegetable>> collector = Collectors.toList();
        List <Vegetable> greenVegetables = greenVegetableStream.collect(collector);

        // 빨간색 야채를 HashSet 자료구조에 저장
        Stream<Vegetable> redVegetableStream = vegetables.stream()
                .filter(s->s.getColor()==Vegetable.Color.RED);
        Supplier<HashSet<Vegetable>> supplier = HashSet::new;
        Collector<Vegetable, ?, HashSet<Vegetable>> collector2 = Collectors.toCollection(supplier);
        Set <Vegetable> redVegetables = redVegetableStream.collect(collector2);

        greenVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
        System.out.println();
        redVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
    }
    public static class Vegetable{
        public enum Color {GREEN, RED,PURPLE}

        String name;
        Color color;

        Vegetable(String name, Color color){
            this.name = name;
            this.color = color;
        }
        String getName(){
            return name;
        }
        Color getColor(){
            return color;
        }
    }
}

결과

위 코드에서 변수를 생량하여 다음과 같이 간단하게 작성할 수 있다.

public class CollectDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN),
                new Vegetable("tomato", Vegetable.Color.RED),
                new Vegetable("redPepper",Vegetable.Color.RED),
                new Vegetable("cabbage",Vegetable.Color.GREEN),
                new Vegetable("eggplant",Vegetable.Color.PURPLE)
        );
        
        // 초록색 야채를 List 자료구조에 저장
        List <Vegetable> greenVegetables = vegetables.stream()
                .filter(s->s.getColor()==Vegetable.Color.GREEN)
                .collect(Collectors.toList());
                
        // 빨간색 야채를 HashSet 자료구조에 저장
        Set<Vegetable> redVegetables = vegetables.stream()
                .filter(s->s.getColor()==Vegetable.Color.RED)
                .collect(Collectors.toCollection(HashSet::new));

        greenVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
        System.out.println();
        redVegetables.stream().forEach(vegetable-> System.out.println(vegetable.getName()));
    }
    public static class Vegetable{
        public enum Color {GREEN, RED,PURPLE}

        String name;
        Color color;

        Vegetable(String name, Color color){
            this.name = name;
            this.color = color;
        }
        String getName(){
            return name;
        }
        Color getColor(){
            return color;
        }
    }
}

 

사용자 정의 컨테이너에 수집

· List, Set, Map과 같은 컬렉션이 아니라 사용자 정의 컨테이너 객체에 수집하는 방법

· 스트림에서는 필터링, 매핑해서 사용자 정의 컨테이너 객체에 수집할 수 있는 collect() 메소드를 추가적으로 제공

인터페이스 리턴 타입 메소드(매개 변수)
Stream R Collect(Supplier<R>, BiConsumer<R,? super T>, BiConsumer<R,R>)
IntStream R Collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R,R>)
LongStream R Collect(Supplier<R>, ObjLongConsumer<R>, BiConsumer<R,R>)
DoubleStream R Collect(Supplier<R>, ObjDoubleConsumer<R>, BiConsumer<R,R>)

· Supplier: 수집될 컨테이너 객체(R)을 생성하는 역할

     - 순차 처리(싱글 스레드) 스트림에서는 단 한 번 Supplier가 실행되고 하나의 컨테이너 객체를 생성

     - 병렬 처리(멀티 스레드) 스트림에서는 여러 번 Supplier가 실행되고 스레드별로 여러 개의 컨테이너 객체를 생성

· 두 번째 Consumer: 컨테이너 객체(R)에 요소(T)를 수집하는 역할

     - 스트림에서 요소를 컨테이너에 수집할 때마다 실행

· 세 번째 Consumer: 컨테이너 객체(R)를 결합하는 역할

     - 병렬 처리 스트림에서 호출되어 스레드별로 생성된 컨테이너 객체를 결합해서 단 하나의 최종 컨테이너 객체를 완성

     - 순차 처리 스트림에서는 호출되지 않음

 

· 리턴 타입 R: 요소들이 최종 수집된 컨테이너 객체

     - 순차 처리 스트림에서 리턴 객체: 첫 번째 Supllier가 생성한 객체

     - 병렬 처리 스트림에서 리턴 객: 최종 결합된 컨테이너 객체

 

 예시 - GreenVegetable 사용자 컨테이너에 데이터 수집

public class GreenVegetable {
    private List<Vegetable> list; //요소를 저장할 컬렉션

    public GreenVegetable(){
        list = new ArrayList<Vegetable>();
        System.out.println("[" + Thread.currentThread().getName() + "] GreenVegetable()");
    }
    // 매개값으로 받은 Vegetable을 list 필드에 수집
    public void accumulate(Vegetable vegetable){ //요소를 수집하는 메소드
        list.add(vegetable);
        System.out.println("[" + Thread.currentThread().getName() + "] accumulate()");
    }
    // 병렬 처리 스트림을 사용할 때 다른 GreenVegetable과 결합할 목적으로 실행
    public void combine(GreenVegetable other){ 
        list.addAll(other.getList());
        System.out.println("[" + Thread.currentThread().getName() + "] combine()");
    }
    public List<Vegetable> getList(){ //요소가 저장된 컬렉션 리턴
        return list;
    }
}

 

public class UserDefinedContainerCollectDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN),
                new Vegetable("tomato", Vegetable.Color.RED),
                new Vegetable("redPepper",Vegetable.Color.RED),
                new Vegetable("cabbage",Vegetable.Color.GREEN),
                new Vegetable("eggplant",Vegetable.Color.PURPLE)
        );

        Stream<Vegetable> greenVegetableStream =  vegetables.stream()
                .filter(s->s.getColor() == Vegetable.Color.GREEN);

        Supplier<GreenVegetable> supplier = () -> new GreenVegetable(); //GreenVegetable을 공급하는 supplier 얻기 
        BiConsumer<GreenVegetable, Vegetable> accumulator = (ms, s) -> ms.accumulate(s); //accumulator로 Vegetable을 수집하는 BiConsumer 얻기 
        //두 개의 Greentable을 매개값으로 받아 combin() 메소드로 결합하는 BiConsumer 얻기. 싱글 스레드에서는 사용 x.
        BiConsumer<GreenVegetable, GreenVegetable> combiner = (ms1, ms2)->ms1.combine(ms2); 

        //GreenVegetable 수집
        GreenVegetable greenVegetable = greenVegetableStream.collect(supplier, accumulator, combiner);

        //수집한 GreenVegetable 출력
        greenVegetable.getList().stream()
                .forEach(s->System.out.println(s.getName()));
    }
}

 

결과

위 람다식을 메소드 참조로 변경하면 다음과 같이 간단하게 작성 가능

public class UserDefinedContainerCollectDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN),
                new Vegetable("tomato", Vegetable.Color.RED),
                new Vegetable("redPepper",Vegetable.Color.RED),
                new Vegetable("cabbage",Vegetable.Color.GREEN),
                new Vegetable("eggplant",Vegetable.Color.PURPLE)
        );

        GreenVegetable greenVegetable = vegetables.stream()
                .filter(s->s.getColor() == Vegetable.Color.GREEN)
                .collect(GreenVegetable :: new, GreenVegetable:: accumulate, GreenVegetable :: combine);

        greenVegetable.getList().stream()
                .forEach(s->System.out.println(s.getName()));
    }
}

 

요소를 그룹핑해서 수집

· collect() 메소드는 요소를 수집하는 기능 이외에 컬렉션 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공

· 사용법: collect()를 호출할 때 CollectorsgroupingBy() 또는 groupingByConcurrent()가 리턴하는 Collector를 매개값으로 대입 

https://steady-coding.tistory.com/318

 예시 - 야채를 색상과 크기로 각각 그룹핑

public class Vegetable{
    public enum Color {GREEN, RED,PURPLE}
    public enum Size {SMALL, MEDIUM, BIG}

    private String name;
    private Color color;
    private Size size;

    Vegetable(String name, Color color, Size size){
        this.name = name;
        this.color = color;
        this.size = size;
    }
    String getName(){
        return name;
    }
    Color getColor(){
        return color;
    }
    Size getSize(){
        return size;
    }
}

 

public class GroupingDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN, Vegetable.Size.BIG),
                new Vegetable("tomato", Vegetable.Color.RED, Vegetable.Size.MEDIUM),
                new Vegetable("redPepper",Vegetable.Color.RED, Vegetable.Size.SMALL),
                new Vegetable("cabbage",Vegetable.Color.GREEN, Vegetable.Size.BIG),
                new Vegetable("eggplant",Vegetable.Color.PURPLE, Vegetable.Size.MEDIUM)
        );

        Function<Vegetable, Vegetable.Color> classifier = Vegetable :: getColor; //Vegetable을 Vegetable.Color로 매핑하는 Function 얻기
        //Vegetable.Color가 키, 그룹핑된 List<Vegetable>가 값인 Map을 생성하는 Collector를 얻기
        Collector<Vegetable, ?, Map<Vegetable.Color, List<Vegetable>>> collector = Collectors.groupingBy(classifier);
        Map<Vegetable.Color, List<Vegetable>> mapByColor = vegetables.stream().collect(collector); //Stream의 collect() 메소드로 Vegetable를 Vegetable.Color별로 그룹핑해서 Map을 얻음


        System.out.print("초록색 야채:");
        mapByColor.get(Vegetable.Color.GREEN).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();
        System.out.print("빨간색 야채:");
        mapByColor.get(Vegetable.Color.RED).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();
        System.out.print("보라색 야채:");
        mapByColor.get(Vegetable.Color.PURPLE).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();

        Function<Vegetable, Vegetable.Size> classifier2 = Vegetable :: getSize;
        Function<Vegetable, String> mapper = Vegetable::getName;
        Collector<String, ?, List<String>> collector2 = Collectors.toList();
        Collector<Vegetable, ?, List<String>> collector3 = Collectors.mapping(mapper,collector2);
        Collector<Vegetable, ?, Map<Vegetable.Size, List<String>>> collector4 = Collectors.groupingBy(classifier2, collector3);

        //collect()의 매개값으로 groupingBy(Function<T, K> classifier, Collector<T, A, D> collector) 사용
        Map<Vegetable.Size, List<String>> mapBySize = vegetables.stream().collect(collector4);
        
        System.out.print("큰 야채:");
        mapBySize.get(Vegetable.Size.BIG).forEach(s-> System.out.print(s + " "));
        System.out.println();
        System.out.print("중간 야채:");
        mapBySize.get(Vegetable.Size.MEDIUM).forEach(s-> System.out.print(s + " "));
        System.out.println();
        System.out.print("작은 야채:");
        mapBySize.get(Vegetable.Size.SMALL).forEach(s-> System.out.print(s + " "));
        System.out.println();
    }
}

 

결과

 

위 코드의 변수를 생략하면 다음과 같이 간단하게 작성 가능

public class GroupingDemo {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",Vegetable.Color.GREEN, Vegetable.Size.BIG),
                new Vegetable("tomato", Vegetable.Color.RED, Vegetable.Size.MEDIUM),
                new Vegetable("redPepper",Vegetable.Color.RED, Vegetable.Size.SMALL),
                new Vegetable("cabbage",Vegetable.Color.GREEN, Vegetable.Size.BIG),
                new Vegetable("eggplant",Vegetable.Color.PURPLE, Vegetable.Size.MEDIUM)
        );

        Map<Vegetable.Color, List<Vegetable>> mapByColor = vegetables.stream()
                .collect(Collectors.groupingBy(Vegetable :: getColor));


        System.out.print("초록색 야채:");
        mapByColor.get(Vegetable.Color.GREEN).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();
        System.out.print("빨간색 야채:");
        mapByColor.get(Vegetable.Color.RED).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();
        System.out.print("보라색 야채:");
        mapByColor.get(Vegetable.Color.PURPLE).forEach(s-> System.out.print(s.getName() + " "));
        System.out.println();


        Map<Vegetable.Size, List<String>> mapBySize = vegetables.stream()
                .collect(
                        Collectors.groupingBy(
                                Vegetable::getSize,
                                Collectors.mapping(Vegetable::getName, Collectors.toList()))
                        );

        System.out.print("큰 야채:");
        mapBySize.get(Vegetable.Size.BIG).forEach(s-> System.out.print(s + " "));
        System.out.println();
        System.out.print("중간 야채:");
        mapBySize.get(Vegetable.Size.MEDIUM).forEach(s-> System.out.print(s + " "));
        System.out.println();
        System.out.print("작은 야채:");
        mapBySize.get(Vegetable.Size.SMALL).forEach(s-> System.out.print(s + " "));
        System.out.println();
    }
}

 

그룹핑 후 매핑 및 집계

· Collectors.groupingBy() 메소드는 그룹핑 후 매핑이나 집계(평균, 카운팅, 연결, 최대, 최소, 합계)를 할 수 있도록 두 번째 매개값으로 Collector를 가질 수 있음

· Collectors는 매핑과 집계를 위한 Collector를 리턴하는 다양한 메소드를 제공(아래) 

리턴 타입 메소드(매개 변수) 설명
Collector<T, ?, R> mapping(Function<T, U> mapper, Collector<U, A, R> collector> T를 U로 매핑한 후, U를 R에 수집
Collector<T, ?, Double> averagingDouble(ToDoubleFunction<T> mapper) T를 Double로 매핑한 후, Double의 평균값을 산출
Collector<T, ?, Long> counting() T의 카운팅 수를 산출
Collector<CharSequence, ?, String> joining(CharSequence delimiter) CharSequence를 구분자로 연결한 String을 산출
Collector<T, ?, Optional<T>> maxBy(Comparator<T> comparator) Comparator를 이용해서 최대 T를 산출
Collector<T, ?, Optional<T>> minBy(Comparator<T> comparator) Comparator를 이용해서 최소 T를 산출
Collector<T, ?, Integer> summingInt(ToIntFunction)
summingLong(ToLongFunction)
summingDouble(ToDoubleFunction)
Int, Long, Double 타입의 합계 산출

 예시 - 야채의 색상별 무게의 평균과 이름 얻기

public class Vegetable{
    public enum Color {GREEN, RED,PURPLE}

    private String name;
    private double kg;
    private Color color;

    Vegetable(String name, double kg, Color color){
        this.name = name;
        this.kg = kg;
        this.color = color;
    }
    String getName(){
        return name;
    }
    Color getColor(){
        return color;
    }
    double getKg() { return kg; }
}

 

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class GroupingAndReductionExample {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",0.3,Vegetable.Color.GREEN),
                new Vegetable("tomato",0.2, Vegetable.Color.RED),
                new Vegetable("redPepper",0.1,Vegetable.Color.RED),
                new Vegetable("cabbage",2.0,Vegetable.Color.GREEN),
                new Vegetable("eggplant",0.5,Vegetable.Color.PURPLE)
        );

        //1. 그룹핑 절차 그대로 보여주기 
        //야채의 색상별 평균 무게를 저장하는 Map 얻기
        Function<Vegetable, Vegetable.Color> classifier = Vegetable :: getColor;
        ToDoubleFunction<Vegetable> mapper = Vegetable::getKg;
        Collector<Vegetable, ?, Double> collector1 = Collectors.averagingDouble(mapper);
        Collector<Vegetable, ?, Map<Vegetable.Color, Double>> collector2 = Collectors.groupingBy(classifier, collector1);
        Map<Vegetable.Color, Double> mapByColor = vegetables.stream().collect(collector2);

        System.out.println("초록색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.GREEN));
        System.out.println("빨간색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.RED));
        System.out.println("보라색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.PURPLE));

        //2. 변수 생략으로 간단하게 표현하기 
        //야채의 색상을 쉽표로 구분한 이름을 저장하는 Map 얻기
        Map<Vegetable.Color, String> mapByName = vegetables.stream()
                .collect(
                        Collectors.groupingBy(
                                Vegetable :: getColor,
                                Collectors.mapping(Vegetable :: getName, Collectors.joining(","))
                        )
                );

        System.out.println("초록색 야채 전체 이름:" + mapByName.get(Vegetable.Color.GREEN));
        System.out.println("빨간색 야채 전체 이름:" + mapByName.get(Vegetable.Color.RED));
        System.out.println("보라색 야채 전체 이름:" + mapByName.get(Vegetable.Color.PURPLE));
    }
}

위 코드의 변수를 생략하면 다음과 같이 간단하게 작성 가능

public class GroupingAndReductionExample {
    public static void main(String[] args){
        List<Vegetable> vegetables = Arrays.asList(
                new Vegetable("broccoli",0.3,Vegetable.Color.GREEN),
                new Vegetable("tomato",0.2, Vegetable.Color.RED),
                new Vegetable("redPepper",0.1,Vegetable.Color.RED),
                new Vegetable("cabbage",2.0,Vegetable.Color.GREEN),
                new Vegetable("eggplant",0.5,Vegetable.Color.PURPLE)
        );

        //야채의 색상별 평균 무게를 저장하는 Map 얻기
        Map<Vegetable.Color, Double> mapByColor = vegetables.stream()
                .collect(
                        Collectors.groupingBy(
                                Vegetable :: getColor,
                                Collectors.averagingDouble(Vegetable :: getKg)
                        )
                );

        System.out.println("초록색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.GREEN));
        System.out.println("빨간색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.RED));
        System.out.println("보라색 야채 전체 평균:" + mapByColor.get(Vegetable.Color.PURPLE));

        //야채의 색상을 쉽표로 구분한 이름을 저장하는 Map 얻기
        Map<Vegetable.Color, String> mapByName = vegetables.stream()
                .collect(
                        Collectors.groupingBy(
                                Vegetable :: getColor,
                                Collectors.mapping(Vegetable :: getName, Collectors.joining(","))
                        )
                );

        System.out.println("초록색 야채 전체 이름:" + mapByName.get(Vegetable.Color.GREEN));
        System.out.println("빨간색 야채 전체 이름:" + mapByName.get(Vegetable.Color.RED));
        System.out.println("보라색 야채 전체 이름:" + mapByName.get(Vegetable.Color.PURPLE));
    }
}

 

반응형

댓글