본문 바로가기
Programming/Java

[자바/Java] 스트림 Stream - 중간 연산

by 코딩하는 랄로 2023. 10. 15.
728x90

https://codingralro.tistory.com/entry/%EC%9E%90%EB%B0%94Java-%EC%8A%A4%ED%8A%B8%EB%A6%BC-Stream-%EC%83%9D%EC%84%B1

 

[자바/Java] 스트림 Stream - 생성

스트림 (Stream) 이란 자바8에서 등장한 스트림은 람다를 활용할 수 있는 개념 중 하나이다. 스트림은 '집합 자료의 연산'을 위해 사용하는 객체로서 스트림이 등장하기 이전에는 배열 또는 컬렉션

codingralro.tistory.com

 

저번 글에서는 자바에서 스트림 객체를 생성하는 법에 대해서 다루었다. 이번 글에서는 생성한 스트림 객체를 어떻게 가공하는지에 대한 과정을 공부해보겠다.

 

 

중간 연산 - Stream 가공하기

스트림은 데이터의 흐름이다. 코딩을 하다보면 데이터의 흐름을 또 다른 데이터의 흐름으로 바꾼다거나 데이터의 흐름 중 필요한 부분만을 추출한다거나, 모든 데이터의 합을 필요하는 등의 상황이 발생한다.

 

이 때에 원하는 결과를 얻기 위해 스트림의 메소드를 통해 데이터를 가공하는 단계가 필요한 것이다. 이러한 가공 단계를 중간 연산이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙이는 chaining이 가능하다.

 

 

filer()

필터는 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업을 한다. 인자로 받는 Predicate는 boolean을 리턴하는 함수형 인터페이스로 평가식이 들어가게 된다. 

 

해당 평가식을 통과한 데이터들을 모은 스트림을 반환한다.

Stream<T> filter(Predicate<? super T> p);

public class Main {
    public static void main(String[] args) {
    
        IntStream intStream = IntStream.range(1, 100); //[1, 2, ..., 99]
        
        //짝수만 추출
        IntStream evenStream =
            intStream.filter(n -> n % 2 == 0); //[2, 4, 6, ..., 96, 98]
            
    }
}

 

 

Map()

map()은 스트림 내 요소들을 하나씩 특정한 값으로 변환해준다. 이 때 값을 변환하기 위한 람다를 인자로 받는다. 이 때 인자로 받는 람다는 Function<T, R> 함수형 인터페이스로서, 매개변수 T를 받아서 R을 리턴한다.

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

public class Main {
    public static void main(String[] args) {
    
        String[] arr = {"Hi", "Map", "Steam"};
        
        Arrays.stream(arr)
            .map(String::length) // 각 요소의 길이
            .forEach(System.out::println); // 2 3 5
            
    }
}

 

 

flatMap()

Map과 동일하게 Fuction<T, R> 함수형 인터페이스를 인자로 받지만 리턴 타입이 Stream이기 때문에 새로운 스트림을 생성해서 리턴하는 람다를 넘겨야 한다.

 

flatMap은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어주는 역할을 하는데 이러한 작업을 플래트닝(flattening)이라고 한다.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> m);

class Student {
    public int kor;
    public int eng;
    public int math;
    
    public Student(int kor, int eng, int math) {
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }
    
    public int getKor() { return this.kor; }
    public int getEng() { return this.eng; }
    public int getMath() { return this.math; }
    
}

public class Main {
    public static void main(String[] args) {
    
        List<List<String>> list = 
            Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d")); //[[a, b], [c, d]]
            
        List<String> flatList = 
            list.stream()
              .flatMap(Collection::stream)
              .collect(Collectors.toList()); //[a, b, c, d]
              
        List<Student> students = Arrays.asList(new Student(100, 80, 90), new Student(30, 40, 50));
        //각 학생들의 평균 출력하기
        students.stream()
            .flatMapToInt(s -> IntStream.of(s.getKor(), s.getEng(), s.getMath()))
            .average().ifPresent(avg -> System.out.println(Math.round(avg * 10)/10.0));
              
    }
}

 

 

Sorted()

정렬을 하는 방법은 다른 정렬과 마찬가지로 Comparator를 이용한다. Comparator 인자 없이 그냥 호출할 경우, 오름차순으로 정렬한다.

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


public class Main {
    public static void main(String[] args) {
    
        IntStream.of(14, 11, 20, 39, 23)
            .sorted()
            .boxed()
            .collect(Collectors.toList()); //[11, 14, 20, 23, 39]
            
        IntStream.of(14, 11, 20, 39, 23)
            .sorted(Comparator.reverseOrder())
            .boxed()
            .collect(Collectors.toList()); //[39, 23, 20, 14, 11]
              
    }
}

 

특정 값을 이용하여 오름차순 또는 내림차순을 하고 싶다면 Comparator의 compare 메소드를 람다로 넘겨주면 된다.

class Student {
    public int kor;
    public int eng;
    public int math;
    
    public Student(int kor, int eng, int math) {
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }
    
    public int getKor() { return this.kor; }
    public int getEng() { return this.eng; }
    public int getMath() { return this.math; }
    
}

public class Main {
    public static void main(String[] args) {
              
        List<Student> students = Arrays.asList(new Student(100, 80, 90), new Student(30, 40, 50));
        
        //학생들 국어 성적 기준으로 오름차순 정렬
        students.stream()
            .sorted((o1, o2) -> o1.getKor() - o2.getKor())
            .collect(Collectors.toList());
            
        //학생들 영어 성적 기준으로 내림차순 정렬
        students.stream()
            .sorted((o1, o2) -> o2.getEng() - o1.getEng())
            .collect(Collectors.toList());
              
    }
}

 

 

Iterating

스트림 내 요소들 각각을 대상으로 특정 연산을 수행하는 메소드로는 peek가 있다. peek는 그냥 확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer를 인자로 받는다.

Stream<T> peek(Consumer<T super T> action;


public class Main {
    public static void main(String[] args) {
    
        int sum = IntStream.of(14, 11, 20, 39, 23)
            .peek(System.out::println) // 14 11 20 39 23
            .sum();
            
        System.out.println(sum); //107
              
    }
}

 

728x90