🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕
*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.
Stream
) 연산스트림에 정의된 메서드 중에서
데이터 소스를 다루는 작업을 수행하는 것을 "연산(Operation)"이라고 한다.
스트림 연산에는
이전 게시물 에서 잠깐 다뤘지만
스트림 만들기 단계를 제외하고
총 2개의 단계가 진행된다.
중간 연산 : 연산 결과가 스트림인 연산 _ 연속해서 중간 연산 가능
[ ex. filter()
, distinct()
, sort()
, limit()
등 등 ]
최종 연산 : 연산 결과가 스트림이 아닌 연산 _ 단 한번만 최종 연산 가능 : " 스트림 소모(closed) "
[ ex. count()
, forEach()
등 등 ]
반환값의 타입 : 무조건
Stream
skip()
말 그대로 Stream 내 요소를 건너뛰는 중간 연산 메서드이다.
long n
개 건너뛰기Stream<T> skip( long n )
// 기본형 스트림에서의 skip _ 반환타입만 다름
IntStream skip( long n )
limit()
반환값/결과값으로 구성된 Stream에 있는 요소에 대해 원하는 갯수만큼만 요소 가져오는 중간 연산 메서드이다.
long maxSize
만큼만 제한해서 가져오기Stream<T> limit( long maxSize )
// 기본형 스트림에서의 limit _ 반환타입만 다름
IntStream limit( long maxSize )
filter()
주어진 조건에 맞는 요소만 가져오는 중간 연산 메서드이다.
filter()
메서드 적용 가능Stream<T> filter( Predicate<? super T> predicate )
IntStream intStream = IntStream.rangeClosed(1,10) ; // 1~10
intStream.filter(i -> i%2 == 0).forEach(System.out::print); // 246810
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
distinct()
Stream내 중복된 요소들을 하나씩만 남겨놓고 중복값 제거하는 중간 연산 메서드이다.
Stream<T> distinct()
IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6) ;
intStream.distinct().forEach(System.out::print); // 123456
sorted()
Stream 요소들을 정렬하는 중간 연산 메서드이다.
Comparator<? super T> comparator
를 통해서 정렬 기준 적용 가능Stream<T> sorted()
Stream<T> sorted( Comparator<? super T> comparator )
이전에 공부했던 것처럼 Comparator
를 지정하지 않으면
스트림의 요소 클래스에서 구현해놓은 Comparable
기준으로 정렬된다.
( 당연히 Comparable
을 구현해놓지 않은 클래스 요소라면 " 지정하지 않을 경우 " 예외가 발생한다. )
*
CASE_INSENSITIVE_ORDER
같은 경우는
미리 static 으로 구현되어 있다.static Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
/*
최종 연산 forEach(System.out::print) 를 수행시켰다고 가정한다.
*/
// 기본 정렬 -> CCaaabccdd
strStream.sorted() ; // String 클래스의 Comparable
strStream.sorted( Comparator.naturalOrder() ) ; // Comparator 클래스에 구현된 기본 정렬 기준
strStream.sorted( (s1, s2) -> s1.compareTo(s2) ) ; // 람다식
strStream.sorted( String::compareTo ) ;
// 역순 정렬 -> ddccbaaaCC
strStream.sorted( Comparator.reverseOrder() )
strStream.sorted( Comparator.<String>naturalOrder().reversed() ) // 잘안씀
// 기본 정렬 (대소문자 구분 X) -> aaabCCccdd
strStream.sorted( String.CASE_INSENSITIVE_ORDER )
// 역순 정렬 (대소문자 구분 X) -> ddCCccbaa _ 주의(대문자가 오히려 앞에 위치)
strStream.sorted( String.CASE_INSENSITIVE_ORDER.reversed() )
// 별도 기준 정렬 : comparing
strStream.sorted( Comparator.comparing( String::length ) )
strStream.sorted( Comparator.comparingInt( String::length ) )
// 별도 기준 역순 정렬 : comparing().reversed()
strStream.sorted( Comparator.comparing( String::length ).reversed() )
Comparator
메서드* JDK1.8 부터 「 Comparator 인터페이스 」에 static메서드와 디폴트 메서드가 많이 추가됨.
해당 메서드들은 모두Comparator<T>
를 반환 ( 가장 기본 메서드 :comparing()
)스트림에서는
sorted()
메서드의 매개변수 값으로 주로 쓰인다
스트림의 요소가 만약 Comparable
을 구현한 경우라면
매개변수가 하나짜리인 comparing
을 사용하면 되고
Comparable
을 구현하지 않은 경우
추가적인 정렬 기준(Comparator
)을 따로 지정해 줘야한다.
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 )
같은 메서드를 통해
" 오토박싱 & 언박싱 " 과 같은 과정을 줄여서 효율적으로 기본형 비교대상을 제공할 수 있다.
만약 정렬 조건이 2개 이상일 경우엔,
정렬 조건 누적을 thenComparing()
메서드로 할 수 있다.
( 사용하는 것은 comparing()
메서드와 크게 다르지 않다. )
thenComparing( Comparator<T> other )
thenComparing( Function<T, U> keyExtractor )
thenComparing( Function<T, U> keyExtractor, Comparator<U> keyComparator )
※ 구조 =
sorted( Comparator.comparing().thenComparing().thenComparing()...thenComparing() )
map()
중간연산 메서드들 중에서 핵심적인 메서드 라고 할 수 있다.
스트림을 사용할 때,
스트림 요소에 저장된 값 중에서
원하는 필드만 뽑아내거나
특정 형태 변환을 해야 할 때가 있는데
이 때, 사용하는 메서드가 map()
메서드이다.
매개변수로는
" T타입 값을 R타입 값으로 변환 & 반환 "하는 함수를 지정해야한다.
// Stream<T> ---> Stream<R>
Stream<R> map( Function<? super T, ? extends R> mapper )
예를 들어
File
타입 값들
즉, File 클래스 객체들로 이루어진 Stream에서
" 파일 이름(String)만 가져와서 이름으로만 구성된 Stream<String>
으로 변환 "
을 하고 싶을 때
Stream<File> fileStream = Stream.of( new File("F1.java"), new File("F2.java"), new File("F3.java") );
// 각 파일 객체 요소에서 파일 이름(String) 값만 가져와 보자.
// fileStream.map( File::getName ).forEach(System.out::print) // F1.javaF2.javaF3.java
Stream<String> fileNameStream = fileStream.map( File::getName );
fileNameStream..forEach(System.out::print) // F1.javaF2.javaF3.java
이 또한 중간 연산 메서드이기 때문에
앞서 알아본 다른 메서드들을 활용해서 같이 사용할 수 있고
특히 이 map()
함수가 요긴하게 쓰인다.
peek()
연산 중간 중간에 디버깅 용도로 많이 쓰이는 메서드
중간 연산과 중간 연산 사이에
스트림을 소모하는 연산
즉, 최종 연산을 사용하지 못하기 때문에
결과를 출력해서 결과를 확인하는 forEach()
를 사용할 수 없다.
그래서 연산 중간 중간의 연산결과를 확인하고 싶을 때,
peek()
함수를 사용한다.
이 peek()
함수는
스트림의 요소를 소모하지 않으므로 여러 번 끼워 넣어도 문제 없다.
Stream<File> fileStream = Stream.of( new File("F1.java"), new File("F1.bak"),
new File("F2.java"), new File("F3.txt"),
new File("F3"), new File("F4.java"));
// 요소 하나씩 하나씩 처리되는 점 주의하자.
fileStream.map( File::getName )
.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) )
.map(String::toUpperCase)
.distinct() // 확장자가 중복되는 경우, 출력하지 못한다.
.forEach(System.out::println);
/*
filename = F1.java
extension = java
JAVA
filename = F1.bak
extension = bak
BAK
filename = F2.java
extension = java
filename = F3.txt
extension = txt
TXT
filename = F4.java
extension = java
*/
flatMap()
" Stream의 Stream "을 Stream으로 변환
정리된 한 줄이 대체 무슨 말인지 이해가 가지 않을 수도 있다.
예를 틀어
배열 요소를 가진 Stream<String[]>
이 있다고 하면
" 스트림 > 배열 > 배열 내부 값들 " 구조가 이루어져 있는데
" 각 배열 "의 내부 값들을 하나의 스트림으로 합병해서
Stream<String>
타입의 하나의 스트림으로 만들고 싶을 때,
만약 바로 위에서 배웠던 map()
메서드를 사용하게 되면
기존의 Stream<String[]>
를 Stream<Stream<String>>
으로 밖에 만들 수 없다.
( 내부 값들이 합쳐진게 아닌 그냥 단순 스트림으로 묶어준 꼴이 된다.
: Stream<Stream<String>> strStrStream = 배열스트림.map( Arrays::stream )
)
이 때,
우리가 원했던 " 각 배열들이 가진 요소들을 하나의 스트림으로 합병 " 과 같은 작업은
flatMap()
메서드를 사용하면 된다.
《Stream<String[]> 스트림》.flatMap( Arrays::stream ) // ---> Stream<String> 의 스트림 반환
보통 문자열 배열의 값들이
긴 문자열 한 줄 씩으로 배열을 구성하고 있다 가정하면
"""
각 한 줄을 단어로 나눠
각 줄의 단어들을 하나의 스트림으로 모아서 취급하고 싶을 때
"""
이럴 때
사용하면 효과적이다.
🔆 중간 연산 메서드 모음 🔆
중간 연산 설명 Stream<T> distinct()
중복 제거 Stream<T> filter( Predicate<T> predicate )
조건에 맞는 요소만 Stream<T> limit( long maxSize )
스트림의 요소 제한 (잘라내기) Stream<T> skip( long n )
스트림의 요소 일부 Skip Stream<T> peek( Consumer<T> action )
스트림의 요소 작업 수행
보통 " 중간 결과를 볼 때 " 사용Stream<T> sorted()
Stream<T> sorted( Comparator<T> comparator )
스트림의 요소 정렬
Comparator
로 정렬 기준
중간 연산 설명 Stream<R>
map( Function<T, R> mapper )
DoubleStream
mapToDouble( ToDoubleFunction<T> mapper )
IntStream
mapToInt( ToIntFunction<T> mapper )
LongStream
mapToLong( ToLongFunction<T> mapper )
스티림 요소 반환 Stream<R>
map( Function<T, R> mapper )
DoubleStream
map( Function<T, R> mapper )
IntStream
map( Function<T, R> mapper )
LongStream
map( Function<T, R> mapper )