여기서부터 진짜 중요한 내용입니다. 실무에서 자주 사용되는 문법이니 꼭 인지하시는 걸 추천드립니다.
1. 스트림의 연산
중간 연산과 최종 연산이 있습니다.
- 중간 연산: 연산결과가 스트림인 연산. 반복적으로 적용 가능합니다.
- 최종 연산: 연산결과가 스트림이 아닌 연산. 단 한번만 적용가능합니다.
Stream.distinct().limit(5).sorted().forEach(System.out::println)
주황색은 중간 연산을 표시하고 3개를 표현했습니다. (N개 가능)
빨간색은 최종 연산이며 1개만 올 수 있습니다.
2. 중간 연산
- distinct(): 중복을 제거한다.
- filter(Predicate<T> predciate): 조건에 안 맞는 요소를 제거
- limit(long maxsize): 일부를 잘라낸다.
- skip(long n): 스트림의 일부를 건너뛴다.
- peek(Consumer<T> action): 작업수행. 중간에 잘 작업했는지 확인할 때 사용.
- sorted() & sorted(Comparator<T> action): 스트림의 요소를 정렬.
- map() & flatMap(): 스트림의 요소를 변환한다.
중간 연산의 핵심은 map()하고 flatMap()이다.
3. 중간 연산 예시
1) 자르기 - skip(), limit()
Stream<T> skip(long n) // 앞에서부터 n개 건너띔
Stream<T> limit(long maxsize) // maxsize 이후의 요소를 잘라냄.
1~10까지의 숫자를 예시로 든다.
IntStream intStream = IntStream.rangedClosed(1,10);
intStream.skip(3).limit(5).forEach(System.out.println);
위 코드의 결과는 어떻게 될까? rangedClosed로 1~10 숫자를 대입한다. 그리고 skip(3)으로 1~3을 건너띄고, limit(5)로 5가지 숫자를 나오게 했다.
그러면 1234567890. 45678만 나온다.
2) 요소 걸러내기 - filter(),distinct()
Stream<T> filter(조건식)
Stream<T> distinct()
filter는 요소에 맞지 않은 요소를 제거하고, distinct는 중복을 제거한다. filter에는 predciate라는 람다식이 들어간다.
IntStream intStream = IntStream.of(1,2,2,2,3,3,3,4,4,5,5,6);
intStream.distinct().forEach(System.out::print);
intStream에는 무슨 값이 나올까? distinct로 중복을 제거해서 1,2,3,4,5,6이 나온다.
IntStream intStream = IntStream.rangeClosed(1,10);
intStream.filter(i->i%2==0).forEach(System.out::print);
intStream은 1~10까지 숫자이다. 근데 filter의 조건문 i%2로 인해 짝수만 남아서 2,4,6,8,10 결과가 나온다.
intStream.filter(i->i%2==0 && i%3!=0).forEach(System.out::print);
intStream.filter(i->i%2==0).filter(i->i%3!=0).forEach(System.out::print);
참고로 &&을 위의 코드처럼 필터 두 개로 표현할 수 있다.
3) 정렬하기 - sorted()
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator>
"CdbacbaacCad"를 stream을 활용해 문자열을 기본 정렬해보면 아래와 같이 표현할 수 있다.
strStream.sorted() //기본 정렬
strStream.sorted(Comparator.naturalOrder()) //기본 정렬
strStream.sorted((s1,s2) -> s1.compareTo(s2)) //람다식도 가능
strStream.sorted(String::compareTo) //람다식
C는 대문자라 아스키 코드 값이 앞에 있어서 출력 결과는 "CCaaaabbccdd"이다.
strStream.sorted(Comparator.reverseOrder()) //기본 정렬 역순
strStream.sorted(Comparator.<String>naturalOrder().reversed())
출력 결과는 역순이므로 "ddccbbaaaaCC"이다.
strStream.sorted(String.CASE_INSENTIVE_ORDER) //대소문자 구분안함
대소문자를 구분 안 하기에 결과는 "aaaabbCCccdd"이다.
이외에도 람다식으로 원하는 정렬을 할 수 있다.
4) 정렬 기준 제공 - comparing()
comparing(Function<T,U> keyExtractor)
comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)
사용 방법은 만약 학생을 반별로 정렬하고 싶다면
studentStream.sorted(Comparator.comparing Student::getClass)).forEach(System.out::println)
위의 코드로 표현할 수 있다.
sorted는 매개변수로 Comparator를 필요로 하기에 Comparator.comparing로 표현했다.
추가 정렬 기준을 제공할 때는 thenComparing()을 사용한다.
studentStream.sorted(Comparator.comparing Student::getClass))
.thenComparing(Student::getTotalScore) // 총점별로 정렬
.thenComparing(Student::getName) //이름별로 정렬
.forEach(System.out::println)
5) 스트림의 요소 변환 - map() (★★★)
자주 사용되는 map이다. 스트림 요소를 변환한다.
Stream<R> map(Function<T,R> mapper)
stream<T>가 stream<R>로 변한다.
만약 File 타입의 fileStream을 map()으로 StringStream으로 바꾸고 싶으면 아래처럼 표현하면 된다.
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 스트림의 모든 파일 이름을 출력.
처음에는 FileStream이였는데 map이라는 메소드를 이용해서 String으로 바꾼다. 이게 map의 역할이다.
만약 파일 스트림에서 파일 확장자(대문자)를 중복없이 뽑아낼라면 어떻게 해야할까?
fileStream.map(File::getName) //Stream<File> -> Stream<String>
.filter(s->s.indexOf('.'!=-1) //확장자 없는거 제외
.map(s->s.subString(s.indexOf('.')+1)) //'.' 이후의 글자. 파일 확장자만.
.map(String::toUpperCase) //대문자 변환
.distinct()
.forEach(System.out::println) //확장자들이 추출.
6) 스트림의 요소를 소비하지 않고 엿보기 - peek()
Stream<T> peek(Consumer<? super T> action) //중간 연산
void forEach(Consumer<? super T> action) //최종 연산
최종 연산과 달리 peek은 스트림을 소비하지 않는다.(forEach는 스트림을 소비한다.)
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) //확장자 출력
.forEach(System.out.println); //최종 연산 스트림을 소비.
peek은 중간 중간에 변환이 잘 되고 있는지 확인할 때 사용한다. 중간 작업 결과를 확인하는 디버깅 용도로 사용한다.
7) 스트림의 스트림을 스트림으로 변환 - flatMap()
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc","def","ghi"},
new String[]{"ABC","GHI","JKLMN"});
Stream<Stream<String>> strArrStrm = strArrStrm.map(Arrays::stream);
위 코드는 아래와 같이 표현된다.
그러나 각각으로 표현하고 싶으면 flatMap을 사용하면 된다.
Stream<String> strArrStrm = strArrStrm.flatMap(Arrays::stream);
여러 개의 문자열 배열을 하나의 문자열로 변화하고 싶으면 flatMap을 사용하면 된다.
String[] strArr ={
"Believe your possible or my power",
"Just do it or believe me"
}
Stream<String> line = Arrays.Stream(strArr);
line.flatMap(line -> Stream.of(line.split(" +")))
.map(String::toLowerCase)
.distinct()
.sorted()
.forEach(System.out::print);
출력 결과는 believe your possible or my power just do it me이다.
이렇게 여러 문장이 있을 때 flatMap으로 표현할 수 있다.
출처: 자바의 정석
'JAVA > JAVA' 카테고리의 다른 글
Optional 개념과 활용 (0) | 2023.01.07 |
---|---|
스트림(Stream) - 최종 연산(3/3) (0) | 2023.01.05 |
스트림(Stream) - 개념(1/3) (0) | 2023.01.03 |
Generics 지네릭스 - 와일드 카드 및 메소드 (2/2) (0) | 2023.01.02 |
Generics 지네릭스 - 개념 및 활용 (1/2) (0) | 2023.01.02 |