본문 바로가기

프로그래밍/JAVA

[ JAVA ] 스트림(stream) - ②

3. 스트림의 중간연산

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

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

// 기본형 스트림에도 skip()과 limit()이 정의되어 있으며, 반환 타입이 기본형 스트림이다.
IntStream skip(long n)
IntStream limit(long maxSize)
skip()과 limit()은 스트림의 일부를 잘라낼 때 사용한다.
ex) skip(3)은 처음 3개의 요소를 건너뛴다.
      limit(5)는 스트림의 요소를 5개로 제한한다.
// 10개의 요소를 가진 스트림에 skip(3)과 limit(5)를 순서대로 적용하면
// 4번째 요소부터 5개의 요소를 가진 스트림이 반환된다.
IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10의 요소를 가진 스트림
intStream.skip(3).limit(5).forEach(System.out::print); // 45678

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

Stream<T> filter(Predicate<? super T> predicate)
Stream<T> distinct()
filter() : 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.
distinct() : 스트림에서 중복된 요소를 제거한다.
// filter()는 매개변수로 Predicate를 필요로 하는데, 연산결과가 boolean인 람다식을 사용해도 된다.
IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10
intStream.filter(i->i%2==0).forEach(System.out::print); // 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); // 157

③ 정렬 -sorted()

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

// ex) 문자열 스트림을 String에 정의된 기본 정렬(사전순 정렬)로 정렬해서 출력한다.
Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");
streStream.sorted().forEach(System.out::print); // CCaaabccdd
▶ sorted() : 스트림을 정렬한다.
▶ sorted()는 지정된 Comparator로 스트림을 정렬하는데, Comparator 대신 int값을 반환하는 람다식을 사용하는 것도 가능하다.
▶ Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬한다. 단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.

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

문자열 스트림 정렬 방법 출력결과
strStream.sorted()                                                // 기본 정렬
strStream.sorted(Comparator.naturalOrder())  // 기본 정렬
strStream.sorted((s1,s2) -> s1.compareTo(s2)) // 람다식도 가능
strStream.sorted(String::compareTo)                // 위의 문장과 동일
CCaabccdd
strStream.sorted(Comparator.reverseOrder()) // 기본 정렬의 역순
strStream.sorted(Comparator.<String>naturalOrder().reversed())
ddccbaaaCC
strStream.sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분안함
참고) String.CASE_INSENSITIVE_ORDER은 String클래스에 정의된 Comparator이다.
aaabCCccdd
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed()) ddCCccbaaa
strStream.sorted(Comparator.comparing(String::length)).    // 길이 순 정렬
strStream.sorted(Comparator.comparingInt(String::length)) // no오토박싱
bddCCccaaa
strStream.sorted(Comparator.Comparing(String::length).reversed()) aaaddCCccb

※ JDK1.8부터 Comparator 인터페이스에 static메서드와 디폴트 메서드가 많이 추가되었는데, 이 메서드들을 이용하면 정렬이 쉬워진다.

<Comparator의 default 메서드>

- reversed()

- thenComparing(Comparator<T> other)

- thenComparing(Function<T,U> keyExtractor)

-thenComparing(Function<T,U> keyExtractor, Comparator<T> 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 comparator)

 

// 가장 기본적인 메서드는 comparing()이다.
// 스트림의 요소가 Comparable을 구현한 경우, 매개변수 하나짜리를 사용
comparing(Function<T,U> keyExtractor)
// 스트림의 요소가 Comparable을 구현하지 않은 경우, 추가적인 매개변수로 정렬기준(Comparator)을 따로 지정
comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)

// 비교대상이 기본형인 경우, comparing()대신 아래의 메서드를 사용하면 오토박싱과 언박싱과정이 없어서 효율적이다.
comparingInt(ToIntFunction<T> keyExtractor)
comparingLong(ToLongFunction<T> keyExtractor)
comparingDouble(ToDoubleFunction<T> keyExtractor)

// 정렬 조건을 추가할 때는 thenComparing()을 사용한다.
thenComparing(Comparator<T> other)
thenComparing(Function<T,U> keyExtractor)
thenComparing(Function<T,U> keyExtractor, Comparator<T> keyComp)

// ex) 학생 스트림(studentStream)을 반(ban)별, 성적(totalScore)순, 이름(name)순으로 정렬하여 출력
studentStream.sorted(Comparator.comparing(Student::getBan)
                               .thenComparing(Student::getTotalScore)
                               .thenComparing(Student::getName))
                               .forEach(System.out::println);

④ 변환 - map()

Stream<R> map(Function<? super T, ? extends R> mapper)
▶ map() : 스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환한다. 매개변수로 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야한다.
// ex) File의 스트림에서 파일의 이름만 뽑아서 출력
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), 
	new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt")); 
// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
// 스트림의 모든 파일이름을 출력
filenameStream.forEach(System.out::println);

// ex) File의 스트림에서 파일의 확장자만을 뽑은 다음 중복을 제거해서 출력한다.
// map()도 filter()처럼 하나의 스트림에 여러 번 적용할 수 있다.
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() : 연산과 연산 사이에 올바르게 처리되었는지 확인할 때 사용한다.
▶ filter()나 map()의 결과를 확인할 때 유용하게 사용될 수 있다.
▶ forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣을 수 있다.
// ex) File의 스트림에서 확장자만 뽑아서 출력
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("extension = %s%n", s)) // 확장자를 출력한다.
          .forEach(System.out::println);

⑥ mapToInt(), mapToLong(), mapToDouble()

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
IntStream    mapToInt(ToIntFunction<? super T> mapper)
LongStream   mapToLong(ToLongFunction<? super T> mapper)
▶ Stream<T>타입의 스트림을 기본형 스트림으로 변환할 때 사용한다.

< 기본형 스트림이 제공하는 메서드 >

메서드 설명
int sum() 스트림의 모든 요소의 총합
OptionalDouble average() sum() / (double)count()
OptionalInt max() 스트림의 요소 중 제일 큰 값
OptionalInt min() 스트림의 요소 중 제일 작은 값
주의) 이 메서드들은 최종연산이기 때문에 호출 후에 스트림이 닫힌다. 즉, 하나의 스트림에 sum()과 average()를 연속해서 호출할 수 없다.
// sum()과 average()를 모두 호출해야할 때, summaryStatistics()라는 메서드를 사용하면 편리하다.
// IntSummaryStatistics는 숫자를 다루는 여러 메서드를 제공한다.
IntSummaryStatistics stat = scoreStream.summaryStatistics();
long totalCount = stat.getCount();
long totalScore = stat.getSum();
double avgScore = stat.getAverage();
int minScore = stat.getMin();
int maxScore = stat.getMax();
// IntStream -> Stream<T>로 변환
Stream<U> mapToObj(IntFunction<? extends U> mapper)
// IntStream -> Stream<Integer>로 변환
Stream<Integer> boxed()

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

// 요소가 문자열 배열인 스트림
Stream<String[]> strArrStrm = Stream.of(
                                        new String[]{"abc","def","ghi"},
                                        new String[]{"ABC","GHI","JKLMN"}
                                        );
// 스트림의 요소를 변환해주는 map()과 배열을 스트림으로 만들어주는 Arrays.stream(T[])를 사용
// map()으로 변환한 결과 Stream<Stream<String>>이다. 즉, 스트림의 스트림이 된다.
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);

// flatMap()으로 변환한 결과 Stream<String>이다.
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);

// 요소의 타입이 Stream<String>인 스트림(Stream<Stream<String>>) -> Stream<String>으로 변환
// toArray()는 스트림을 배열로 변환해서 반환한다. 매개변수를 지정하지 않으면 Object[]을 반환함.
Stream<String> strStream = strStrStrm
       .map(s -> s.toArray(String[]::new)) // Stream<Stream<String>> -> Stream<String[]>
       .flatMap(Arrays::stream); // Stream<String[]> -> Stream<String>