본문 바로가기
java

Lambda 06 Stream의 연산 (중간 연산과 최종 연산)

by 킹차니 2021. 12. 29.

스트림의 중간 연산

 

스트림의 최종 연산

최종 연산 설명
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer action)
각 요소에 지정된 작업 수행
long count() 스트림의 요소 개수 반환
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
스트림의 최대, 최소값 반환
Optioanl<T> findAny()  //아무거나 하나
Optional<T> findFirst() // 첫 번째 요소 
스트림의 요소 하나 반환
boolean allMatch(Predicate<T> p) //모두 만족하는지
boolean anyMatch(Predicate<T> p) //하나라도 만족하는지
boolean noneMatch(Predicate<T> p) //모두 만족하지 않는지
주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인
Object[] toArray()
A[] toArray(IntFunction<A[]> generator)
스트림의 모든 요소를 배열로 반환
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator,BinaryOperator<T> combiner)
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산한다.
R collect(Collector<T, A, R> collector)
R collect(Supplier<R> supplier, BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner)
스트림의 요소를 수집한다.
주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데에 사용된다.

 

 

Stream의 중간연산

문자열 스트림 정렬 방법 출력결과
strStream.sorted() //기본정렬
strStream.sorted((s1, s2)->s1.compareTo(s2)); //람다식도 가능
CCaaabccdd
strStream.sorted(Comparator.reverseOrder()) //기본 정렬의 역순 ddccbaaaCC
strStream.sorted(String.CASE_INSENSITIVE_ORDER) //대소문자 구분안함 aaabCCccdd
strStream.sorted(Comparator.comparing(String::length)) //길이 순 정렬
strStream.sorted(Comparator.comparingInt(String::length)) //no오토박싱
bddCCccaaa
strStream.sorted(Comparator.comparing(String::length).reversed()) //길이 역순 정렬 aaaddCCccb

 

 

 


중간 연산

 

중간연산들에 대해서 하나씩 알아보자.

 

skip(), limit()

Stream<T> skip(long n) //앞에서부터 n개 건너뛰기
Stream<T> limit(long maxSize) //maxSize 이후의 요소는 잘라냄

Instream.rangeClosed(1, 10); //12345678910
intStream.skip(3).limit(5).forEach(System.out::println); //45678(skip에 의해 4부터, limit에 의해 5개만 나온다.)

 

filter() - 조건에 따라 걸러내기

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

Instream.rangeClosed(1, 10); //12345678910
intStream.filter(i->i%2==0).forEach(System.out::println)//246810
intStream.filter(i->i%2!=0 && i%3!=0).forEach(System.out::println)

//filter는 중간 연산이라 당연히 여러번 사용가능하다.
intStream.filter(i->i%2!=0).filter(i->i%3!=0).forEach(System.out::println);//157

 

comparing()

Comparator의 comparing()으로 정렬 기준을 제공

//comparing은 Comparator를 반환타입으로 한다.
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

studentStream.sorted(Comparator.comparing(Student::getBan)).forEach(System.out::println);//반별로 정렬

추가 정렬 기준을 제공할 때는 thenComparing()을 사용

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

studentStream.sorted(Comparator.comparing(Student::getBan)//반별로 정렬
					.thenComparing(Student::getTotalScore)//총점별로 정력
					.thenComparing(Student::getName)//이름별로 정렬
					.forEach(System.out::println);

EX)

< sorted, fiter 사용해보기 >

public class Ex1408 {
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("이자바", 3, 300),
                new Student("김자바", 1, 230),
                new Student("남자바", 2, 140),
                new Student("조자바", 1, 190),
                new Student("장자바", 3, 270),
                new Student("왕자바", 2, 290),
                new Student("황자바", 1, 210)
        );
studentStream.sorted(Comparator.comparing(Student::getBan)//반별로 정렬
                        .thenComparing((Student s)->s.getTotalScore()))//총점별로 정렬
                        .filter((Student s)-> s.getTotalScore() < 200)//총점200이하만 걸러내기
                        .forEach(System.out::println);
    }

    private static class Student {
        String name;
        int ban;
        int totalScore;
        public Student(String name, int ban, int totalScore) {
            this.name = name;
            this.ban = ban;
            this.totalScore = totalScore;
        }
        public String getName() {return name;}
        public int getBan() {return ban;}
        public int getTotalScore() {return totalScore;}
        @Override
        public String toString() {
            return "["+name+" "+ban+" "+totalScore+"]";
        }
    }
}

실행결과:
[조자바 1 190]
[남자바 2 140]

 

map()

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

Stream<T>을 -> Stream<R>로 바꿔준다. 

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

        File[] fileArr = {new File("ex1.java")
                         ,new File("ex1.bak")
                         ,new File("ex1")
                         ,new File("ex1.txt")};

        Stream<File> fileStream = Stream.of(fileArr);

        //map으로 file 스트림을 string 스트림으로 변경
        Stream<String> filenameStream = fileStream.map(File::getName);
        filenameStream.forEach(System.out::println); //스트림의 모든 파일의 이름을 출력

        //스트림 다시 생성
        fileStream = Stream.of(fileArr);

        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
        System.out.println();
    }
}

실행결과:
ex1.java
ex1.bak
ex1
ex1.txt
JAVABAKTXT

 

peek() 

스트림의 요소를 소비하지 않고 엿보기 (중간에 연산 확인을 위해 디버깅 용도로 사용한다.)

Stream<T> peek(Consumer<? super T> action) //중간연산

void forEach(Consumer<? super T> action) //최종연산

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.indexOf('.')+1)//대문자로 변환
                .peek(s -> System.out.printf("extension=%s%n", s)) //확장자를 출력
                .forEach(System.out::print); //JAVABAKTXT
//Stream
public class Ex1410 {
    public static void main(String[] args) {

        File[] fileArr = {new File("ex1.java")
                         ,new File("ex1.bak")
                         ,new File("ex1")
                         ,new File("ex1.txt")};

        Stream<File> fileStream = Stream.of(fileArr);

        //map으로 file 스트림을 string 스트림으로 변경
        Stream<String> filenameStream = fileStream.map(File::getName);
        filenameStream.forEach(System.out::println); //스트림의 모든 파일의 이름을 출력

				System.out.println("-----------------");

        //스트림 다시 생성
        fileStream = Stream.of(fileArr);

        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)) //확장자를 출력
                .map(String::toUpperCase)//모두 대문자로 변환
                .distinct()
                .forEach(System.out::println); //JAVABAKTXT
        System.out.println();
    }
}

실행결과:

ex1.java
ex1.bak
ex1
ex1.txt
-----------------
filename=ex1.java
extension=java
JAVA
filename=ex1.bak
extension=bak
BAK
filename=ex1.txt
extension=txt
TXT

 

flatMap()

스트림의 스트림을 스트림으로 변환

Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"},
																				new String[]{"ABC", "GHI", "JKLMN"});

Stream<Stream<String>> strStrm = strArrStrm.map(Arrays::stream); //스트림안에 스트림

//하지만 현재 원하는 것은 strArrStrm의 모든 문자열들이 합쳐진 Stream<String>을 만들고 싶다.
//이때 flatMap을 쓴다.
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); //Arrays.stream(T[])
// Stream<String> ==> < "abc", "def", "ghi", "ABC", "GHI", "JKLMN" > 이렇게 된다.

EX)

//Stream
public class Ex1411 {
    public static void main(String[] args) {

        Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"},
                new String[]{"ABC", "GHI", "JKLMN"});

        Stream<Stream<String>> strStrm = strArrStrm.map(Arrays::stream); //스트림안에 스트림
        strStrm.forEach(System.out::println);
		//실행결과:
        //java.util.stream.ReferencePipeline$Head@5a39699c
        //java.util.stream.ReferencePipeline$Head@3cb5cdba

        Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); //Arrays.stream(T[])
        strStrStrm.forEach(System.out::println);
		//실행결과:
        //abc
        //def
        //ghi
        //ABC
        //GHI
        //JKLMN

        String[] lineArr = {"Believe or not It is true", "Do or do not There is no try"};
        Stream<String> lineStream = Stream.of(lineArr);

        Stream<String> stringStream = lineStream.flatMap((line) -> Stream.of(line.split(" +")));//" +"는 하나 이상의 공백을 의미하는 정규식
        stringStream.map(String::toLowerCase)
                .distinct()
                .sorted()
                .forEach(System.out::println);
        System.out.println();
	
		//실행결과:
        //believe
	    //do
	    //is
	    //it
	    //no
	    //not
	    //or
	    //there
	    //true
	    //try
    }
}

 


 

최종 연산

 

스트림의 최종연산은 아래와 같은 특징을 가진다.

1. 1번만 가능하며

2. 스트림의 요소들을 소모한다.

3. 최종연산을 진행하고 나면 스트림은 닫힌다.

4. int, boolean, Optional 같은 타입을 반환한다. (중간 연산은 Stream을 반환한다.)

 

 

forEach

void forEach(Consumer<? super T> action) //병렬스트림인 경우 순서가 보장되지 않음
void forEachOrdered(Consumer<? super T> action)  //병렬스트림인 경우에도 순서가 보장됨

//sequential()은 스트림의 작업을 직렬로 처리하도록 해준다. (생략해도 적용된다)
IntStream.range(1, 10).sequential().forEach(System.out::print);//123456789
IntStream.range(1, 10).sequential().forEachOrdered(System.out::print);//123456789

//병렬스트림으로 처리한다. 다중 스레드.
IntStream.range(1, 10).parallel().forEach(System.out::print);//679324185
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print);//123456789

 

조건 검사 - allMathch(), anyMatch(), noneMatch()

boolean allMathch (Predicate<? super T> predicate) //모든 요소가 조건을 만족시키려면 true
boolean anyMatch (Predicate<? super T> predicate)  //한 요소라도 조건을 만족시키면 true
boolean noneMatch (Predicate<? super T> predicate) //모든 요소가 조건을 만족시키지 않으면 true
//학생들 중 80점보다 낮으 점수를 받은 학생이 한명이라도 존재하는가?
boolean hasFailedStudent 
 = studentStream.anyMatch((student)->student.getScore()< 80);

 

조건에 일치하는 요소 찾기 - findFirst(), findAny() (둘다 같은 기능을 한다.)

Optioanl<T> findFirst() //첫 번째 요소를 반환. 순차 스트림에 사용
Optional<T> findAny()   //아무거나 하나를 반환. 병렬 스트림에 사용
//학생들 중 점수가 80이상인 학생 아무나
Optional<Student> result = studentStream.filter(s -> s.getScore() < 80).findFirst();
Optional<Student> result = parallelStudentStream.filter(s -> s.getScore() < 80).findAny();//병렬스트림

 

reduce() - 스트림의 요소를 하나씩 줄여가며 누적연산 수행 (accumulator)

identity - 초기값

accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산

combiner - 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림)

Optioanl<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BinaryFunction<U, T, U> accumulator, BinaryOperator<U> combiner)
//int reduce(int identity, IntBunaryOperator op)
int count = intStream.reduce(0, (a,b) -> a + 1);  //count()
int sum = intStream.reduce(0, (a,b) -> a + b);   //sum()
int max = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a > b ? a : b); //max()
int min = intStream.reduce(Integer.MAX_VALUE, (a,b) -> a < b ? a : b); //min()

위의 sum은 아래와 같다.

int a = identity; //identity = 0;

for(int b : stream)
	a = a + b; // sum()

 

 

EX)

import java.util.Arrays;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Stream;


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

        String[] strArr = {"inheritance", "java", "lambda", "stream", "optionalDouble", "intStream", "count", "sum"};
        Stream.of(strArr).forEach(System.out::println);

        boolean noEmptyStr = Stream.of(strArr).noneMatch(s -> s.length() == 0);
        Optional<String> sWord1 = Stream.of(strArr).filter((s) -> s.charAt(0) == 's').findFirst();
        Optional<String> sWord2 = Stream.of(strArr).parallel().filter((s) -> s.charAt(0) == 's').findAny();//stream이 나올수도, sum이 나올수도있다.
        System.out.println("noEmptyStr = " + noEmptyStr);//true
        System.out.println("sWord1 = " + sWord1.get());//stream
        System.out.println("sWord2 = " + sWord2.get());//sum 또는 stream

        //Stream<String>을 IntStream으로 변환. IntStream 기본형 스트림 : { 11, 4, 6, 6, 14, 9, 5, 3 }을 가진 스트림이된다.
        IntStream intStream1 = Stream.of(strArr).mapToInt(String::length);
        IntStream intStream2 = Stream.of(strArr).mapToInt(String::length);
        IntStream intStream3 = Stream.of(strArr).mapToInt(String::length);
        IntStream intStream4 = Stream.of(strArr).mapToInt(String::length);

        int count = intStream1.reduce(0, (a, b) -> a + 1);// 총 단어 몇개?
        int sum = intStream2.reduce(0, (a, b) -> a + b);//모든 단어 길이의 합
        OptionalInt max = intStream3.reduce(Integer::max);//가장 긴 단어의 길이
        OptionalInt min = intStream4.reduce(Integer::min);//가장 짧은 단어의 길이

        System.out.println("count = " + count); //8
        System.out.println("sum = " + sum);//58
        System.out.println("max = " + max);//OptionalInt[14]
        System.out.println("min = " + min);//OptionalInt[3]
    }
}

 

 

 

출처: 남궁성님 유튜브 강의
https://www.youtube.com/user/MasterNKS

'java' 카테고리의 다른 글

Lambda 07 Collector  (0) 2021.12.30
Optional  (0) 2021.12.30
Lambda 05 Stream  (0) 2021.12.27
Lambda 04 메소드 참조  (0) 2021.12.26
Lambda 03 Predicate의 결합  (0) 2021.12.26