ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Stream(스트림) - 3. 스트림 중간 연산
    Java 2020. 3. 9. 22:10
    반응형

    스트림 자르기 - skip(), limit()

     skip()과 limit()은 스트림의 일부를 잘라낼 때 사용

    Stream<T> skip(long n)        // 처음 n개의 요소를 건너 뜀
    Stream<T> limit(long maxSize) // 스트림의 요소를 maxSize만큼 제한

     예시) 10개의 요소를 가진 스트림에 skip(3)과 limit(5)를 순서대로 적용하면 4번째 요소부터 5개의 요소를 가진 스트림이 반환됨

    IntStream intStream = IntStream.rangeClose(1, 10); // 1 ~ 10의 요소를 가진 스트림
    intStream.skip(3).limit(5).forEach(System.out::print); // 45678

     기본형 스트림에도 skip()과 limit()이 정의되어있음. 반환 타입이 기본형 스트림이라는 점만 다름

    IntStream skip(long n)
    IntStream limit(long maxSize)

    스트림 요소 걸러내기 - filter(), distinct()

     disctinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건(Predicate)에 맞지 않는 요소들을 걸러냄

    Stream<T> filter(Predicate<? super T> predicate)
    Stream<T> distinct()

    distinct() 사용법

    IntStream intStream = IntStream.of(1, 2, 2, 3, 3, 3, 4, 5, 5, 6);
    intStream.distinct().forEach(System.out::print); // 12345

    filter() 사용법

     매개변수로 Predicate를 필요로 함

     연산결과가 boolean인 람다식을 사용하더라도 무관함

    IntStream intStream = IntStream.rangeClose(1, 10); // 1 ~ 10
    intStream.filter(i -> i % 2 == 0).forEach(System.out::println); // 246810

    필요하다면 filter()를 다른 조건으로 여러 번 사용할 수도 있음

    // 아래의 두 문장은 동일한 결과를 얻음)
    intStream.filter(i -> i%2!=0 && i%3!=0).forEach(System.out::print); // 157
    intStream.filter(i -> i%2!=0).filter(i -> i%3!=0).forEach(System.out::print);

    정렬 - sorted()

     스트림을 정렬할 때는 sorted()를 사용하면 됨

    Stream<T> sorted()
    Stream<T> sorted(Comparator<? super T> comparator)

     sorted()는 지정된 Comparator로 스트림을 정렬하는데, Comparator 대신 int값을 반환하는 람다식을 사용하는 것도 가능, Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬. 단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외 발생

    Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
    strStream.sorted().forEach(System.out::print); // CCaaabccdd

     위의 코드는 문자열 스트림을 String에 정의된 기본 정렬(사전순 정렬)로 정렬해서 출력, 아래는 위의 문자열 스트림(strStream)을 다양한 방법으로 정렬한 후에 forEach(System.out::print)로 출력한 결과

    String.CASE_INSENSITIVE_ORDER > String에 정의된 Comparator

     문자열 스트림을 정렬하는 다양한 방법

    /* 기본 정렬 - 출력결과 : CCaaabccdd */
    strStream.sorted()
    strStream.sorted(Comparator.naturalOrder())
    strStream.sorted((s1, s2) -> s1.compareTo(s2))  // 람다식도 가능
    strStream.sorted(String::compareTo)             // 위의 문장과 동일
    
    /* 기본 정렬의 역순 - 출력결과 : ddccbaaaCC */
    strStream.sorted(Comparator.reverseOrder())
    strStream.sorted(Comparator.<String>naturalOrder().reversed())
    
    /* 대소문자 구분안함 - 출력결과 : aaabCCccdd */
    strStream.sorted(String.CASE_INSENSITIVE_ORDER)
    
    /* 대소문자 구분안함 역순(단 이경우도 대문자 먼저) - 출력 결과 : ddCCccbaaa */
    strStream.sorted(String.CASE_INSENSITIVE_ORDER.REVERSED())
    
    /* 길이 순 정렬 - 출력결과 : bddCCccaaa */
    strStream.sorted(Comparator.comparing(String::length))
    strStream.sorted(Comparator.comparingInt(String::length)) // no오토박싱
    
    /* 길이 순 정렬의 역순 - 출력결과 : aaaddCCccb */
    strStream.sorted(Comparator.comparing(String::length).reversed())

    Comparator

     JDK1.8 ~ Comparator인터페이스에 static 메서드와 디폴트 메서드가 많이 추가되었고, 해당 메서드들을 이용하면 정렬이 쉬워짐. 모두 Comparator<T>를 반환함

     제네릭에서 와일드 카드를 제거하여 간단히한 Comparator의 메서드들(자세한 것은 JAVA API 문서 참고)

    /* Comparator의 default메서드 */
      reversed()
      thenComparing(Comparator<T> other)
      thenComparing(Function<T, U> keyExtractor)
      thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)
      thenComparingInt(ToIntFunction<T> keyExtractor)
      thenComparingLong(ToLongFunction<T> keyExtractor)
      thenComparingDouble(ToDoubleFunction<T> keyExtractor)
      
    /* Comparator의 static메서드 */
      naturalOrder()
      reverseOrder()
      comparing(Function<T, U> keyExtractor)
      comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
      comparingInt(ToIntFunction<T> keyExtractor)
      comparingLong(ToLongFunction<T> keyExtractor)
      comparingDouble(ToDoubleFunction<T> keyExtractor)
      nullsFirst(Comparator<T> comparator)
      nullsLast(Comparator<T> comparator)

     가장 기본적인 메서드 - comparing()

    // 스트림의 요소가 Comparable을 구현한 경우
    comparing(Function<T, U> keyExtractor)
    
    /* 스트림의 요소가 Comparable을 구현하지 않은 경우
     * 추가적인 매개변수로 정렬기준(Comparator)을 따로 지정해줘야 함
     */
    comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

      비교대상이 기본형인 경우, 아래 메서드를 사용하면 오토박싱과 언박싱과정이 없어서 더 효율적임

    comparingInt(ToIntFunction<T> keyExtractor)
    comparingLong(ToLongFunction<T> keyExtractor)
    comparingDouble(ToDoubleFunction<T> keyExtractor)

     정렬 조건을 추가할 때는 thenComparing()을 사용

    thenComparing(Comparator<T> ohter)
    thenComparing(Function<T, U> keyExtractor)
    thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)

     예) 학생 스트림(studentStream)을 반(ban)별, 성적(totalScore)순, 그리고 이름(name)순으로 정렬하여 출력하려면 다음과 같이 하면 됨

    studentStream.sorted(Comparator.comparing(Student::getBan)
                              .thenComparing(Student::getTotalScore)
                              .thenComparing(Student::getName))
                              .forEach(System.out::println);

    변환 - map()

     스트림의 요소에 저장된 값 중 원하는 필드만 추출하거나 특정 형태로 변환해야 할 때 사용

     메서드 선언부는 아래와 같으며, 매개변수로 T 타입을 R 타입으로 변환해서 반환하는 함수로 지정해야 함

    Stream<R> map(Function<? super T, ? extends R> mapper)

     map() 역시 중간 연산, 연산 결과는 String을 요소로 하는 스트림

     예) File의 스트림에서 파일의 이름만 뽑아서 출력하고 싶을 때, 아래와 같이 map()을 이용하면 File객체에서 파일의 이름(String)만 간단히 뽑아낼 수 있음

    - 아래의 경우엔 map()으로 Stream<File>을 Stream<String>으로 전환했다고 보면 됨

    Stream<File> fileStream = Stream.of(new File("Exam01.java"), new File("Exam01"),
             new File("Exam01.bak"), new File("Exam02.java"), new File("Exam01.txt"));
             
    // map()으로 stream<File>을 Stream<String>으로 변환
    Stream<String> filenameStream = fileStream.map(File::getName);
    filenameStream.forEach(System.out::println); // 스트림의 모든 파일이름을 출력

     map()도 filter()처럼 하나의 스트림에 여러번 적용할 수 있음

     예) File의 스트림에서 파일의 확장자만을 뽑은 후 다음 중복을 제거해서 출력

    fileStream.map(File::getName)             // Stream<File> -> Stream<String>(파일명가져옴)
      .filter(s -> s.indexOf('.')!= -1)       // 확장자가 없는 것은 제외
      .map(s -> s.substring(s.indexOf('.')+1) // Stream<String> -> Stream<String>(.뒤로만)
      .map(String::toUpperCase)               // 모두 대문자로 변환
      .distinct()                             // 중복 제거
      .forEach(System.out::print);            // JAVABAKTXT

    조회 - peek()

     연산과 연산 사이에 올바르게 처리되었는지 확인하고 싶다면, peek() 사용

     forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워넣어도 문제가 되지 않음

     filter()나 map()의 결과를 확인할 때 유용하게 사용될 수 있음

    fileStream.map(File::getName)             // Stream<File> -> Stream<String>(파일명가져옴)
      .filter(s -> s.indexOf('.')!= -1)       // 확장자가 없는 것은 제외
      .peek(s -> System.out.printf("filename = %s%n", s)) // 파일명 출력
      .map(s -> s.substring(s.indexOf('.')+1) // 확장자만 추출
      .peek(s -> System.out.printf("extendsion = %s%n", s)) // 확장자 출력
      .forEach(System.out::print);

    mapToInt(), mapToLong(), mapToDouble()

     map()은 연산의 결과로 Stream<T> 타입의 스트림을 반환

     스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있음

     Stream<T> 타입의 스트림을 기본형 스트림으로 변환할 때 사용하는 메서드

    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
    IntStream    mapToInt(ToIntFunction<? super T> mapper)
    LongStream   mapToLong(ToLongFunction<? super T> mapper)

     사용 예제 - 학생 성적 가공

    /* Stream<Integer> */
    Stream<Integer> studentScoreStream = studentStream.map(Student::getTotalScore);
    
    /* IntStream */
    IntStream studentScoreStream = StudentStream.map(Studnet::getTotalScore);
    // int sum; - 성적에 대하여 형변환하지 않고 바로 구할 수 있음
    int allTotalScore = studnetScoreStream.sum();

    기본형 스트림이 제공하는 편리한 메서드

     count()만 지원하는 Stream<T>와 달리 IntStream과 같은 기본형 스트림(LongStream, DoubleStream)은 숫자를 다루는데 편리한 메서드들을 제공(Stream에 따라 반환 타입이 다름)

    max()와 min()은 Stream에도 정의되어 있지만, 매개변수로 Comparator를 지정해야 된다는 차이가 있음
    /* IntStream */
    int            sum()       스트림 모든 요소의 총합
    OptionalDouble average()   sum() / (double) count()
    OptionalInt    max()       스트림의 요소 중 제일 큰 값
    OptionalInt    min()       스트림의 요소 중 제일 작은 값

     스트림의 요소가 하나도 없을 때 sum()은 0을 반환하면 되지만, 다른 메서드들은 단순히 0을 반환할 수 없음

    = 여러 요소를 합한 평균이 0일 수도 있기 때문

     이를 구분하기 위해서 double을 반환하는 대신, double 타입 값을 내부적으로 가지고 있는 OptionalDouble을 반환

     OptionalDouble, OptionalInt는 일종의 Wrapper 클래스로 각각 int값과 double값을 내부적으로 가지고 있음

    summaryStatistics() 메서드

     위의 편리한 메서드들은 최종연산이므로 호출 후에 스트림이 닫힘(= 하나의 스트림에 sum()과 average()를 연속해서 사용할 수 없음) => 그래서 사용하는 것이 summaryStatistics() 메서드

     IntSummaryStatistics는 아래와 같이 다양한 종류의 메서드를 제공, 필요한 것만 골라서 사용하면 됨

    IntSummerayStatistics stat = scoreStream.summaryStatistics();
    
    long   totalCount = stat.getCount();
    long   totalSum   = stat.getSum();
    double totalAvg   = stat.getAverage();
    int    totalMin   = stat.getMin();
    int    totalMax   = stat.getMax();

    기본형 Stream의 Stream<T> or Stream<기본형의 Wrapper클래스> 변환

     Stream<T> : mapToObj

     Stream<기본형의 Wrapper클래스> : boxed()

    /* IntStream */
    Stream<U>       mapToObj(IntFunction<? extends U> mapper)
    Stream<Integer> boxed()

     intStream을 Stream<String>으로 변환한 예

    IntStream intStream = new Random().ints(1, 46); // 1~46까지의 정수
    Stream<String> lottoStream = intStream.distinct().limit(6).sorted()
                                    .mapToObj(i -> i + ", ");
    lottoStream.forEach(System.out::print); // 12, 14, 21, 29, 32, 45

     CharSequence.chars() - String이나 StringBuffer에 저장된 문자들을 IntStream으로 다룰 수 있게 해줌

    IntStream charStream = "12345".chars(); // default IntStream chars()
    int charSum = charStream.map(ch -> ch-'0').sum(); // charSum = 15

     mapToInt()와 함께 Integer.parseInt()나 valueOf()도 많이 쓰임

    Stream<String>  -> IntStream 변환시 mapToInt(Integer::parseInt)
    Stream<Integer> -> IntStream 변환시 mapToInt(Integer::intValue)

    flatMap() - Stream<T[]>를 Stream<T>로 변환

     스트림의 요소가 배열이거나, map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루는 것이 더 편리할 때가 있음

     위와 같을 때는 map() 대신 flatMap()을 사용하면 됨

    예시 - 아래 문자열들을 합쳐서 문자열이 요소인 스트림, Stream<String>으로 만들려고 함

    Stream<String[]> strArrStream = Stream.of(
             new String[]{"J", "A", "M", "I", "E"},
             new String[]{"j", "a", "m", "i", "e"},
    };
    
    /* 구조 */
    Stream<String[]>
    ┃ {"J", "A", "M", "I", "E"} ┃ {"j", "a", "m", "i", "e"} ┃

      시도 1. Arrays.stream(T[]) 이용 - 배열을 스트림으로 만들어줌

    - 예상 : Stream<String> 반환

    - 결과 : Stream<Stream<String>> 반환 - 스트림의 스트림을 반환

    Stream<Stream<String>> strStrStream = strArrStream.map(Arrays::stream);
    
    /* 구조 */
    Stream<Stream<String>>
    ┃ │ "J" │ "A" │ "M" │ "I" │ "E" │ ┃ │ "j" │ "a" │ "m" │ "i" │ "e" │ ┃

     시도 2. flatMap() 사용

    Stream<String> strStream = strArrStream.flatMap(Arrays::stream);
    
    /* 구조 */
    Stream<String>
    ┃ "J" ┃ "A" ┃ "M" ┃ "I" ┃ "E" ┃ "j" ┃ "a" ┃ "m" ┃ "i" ┃ "e" ┃

    예시 - 여러 문장을 요소로 하는 스트림을 split하여 요소가 단어인 스트림을 만들기

    String[] lineArr = {
        "Jamie is a student",
        "Jamie study JAVA",
    };
    
    Stream<String> lineStream = Arrays.stream(lineArr);
    ┃ "Jamie is a student" ┃ "Jamie study JAVA" ┃
    
    Stream<Stream<String>> strArrStream = lineStream
                            .map(line -> Stream.of(line.split(" +")));
    ┃ │ "Jamie" │ "is" │ "a" │ "student" │ ┃ │ "Jamie" │ "study" │ "JAVA" │ ┃
    
    Stream<String> strStream = lineStream
                         .flatMap(line -> Stream.of(line.split(" +")));
    ┃ "Jamie" ┃ "is" ┃ "a" ┃ "student" ┃ "Jamie" ┃ "study" ┃ "JAVA" ┃

    예시 - 소문자 변환 & 중복 제거 & 오름차순 정렬 & 화면 출력

    strStream.map(String::toLowerCase)      // 모든 단어를 소문자로 변경
             .distinct()                    // 중복된 단어 제거
             .sorted()                      // 사전 순 정렬
             .forEach(System.out::println); // 화면 출력

    예시 - (간혹) 스트림을 요소로 하는 스트림, 즉 스트림의 스트림을 하나의 스트림으로 합칠 때 사용

     toArray() : 스트림을 배열로 변환하여 반환, 매개변수를 지정하지 않으면 Object[]를 반환하므로 특정 타입의 생성자를 지정해줄 것(아래 예시에서는 String 배열의 생성자 - String[]::new를 지정)

    Stream<String> strStream1 = Stream.of("Jamie", "is", "a", "student");
    Stream<String> strStream2 = Stream.of("She", "is", "a", "student");
    ┃ │ "Jamie" │ "is" │ "a" │ "student" │ ┃
    ┃ │ "She" │ "is" │ "a" │ "student" │ ┃
    
    Stream<Stream<String>> streamStream = Stream.of(strStream1, strStream2);
    ┃ │ "Jamie" │ "is" │ "a" │ "student" │ ┃ │ "She" │ "is" │ "a" │ "student" │ ┃
    
    Stream<String> strStream = streamStream
      .map(s -> s.toArray(String[]::new)) // Stream<Stream<String> -> Stream<String[]>
      .flatMap(Arrays::stream);           // Stream<String[]> -> Stream<String>
    ┃ "Jamie" ┃ "is" ┃ "a" ┃ "student" ┃ "She" ┃ "is" ┃ "a" ┃ "student" ┃
    
    반응형

    댓글

Designed by Tistory.