상세 컨텐츠

본문 제목

[Java] 8. Stream의 개념과 사용 방법-1

프로그래밍 언어/Java(자바)

by Guroo 2023. 9. 6. 13:32

본문

[Java] 7. 메소드 참조 (::)

 

[Java] 7. 메소드 참조 (::)

6. Map에 정의된 추상 메소드와 default 메소드 6. Map에 정의된 추상 메소드와 default 메소드 6-1. Map에 정의된 추상 메소드와 default 메소드 이번에 살펴볼 인터페이스는 Map이다. Map은 아래의 그림에서

sesoc.tistory.com

 

8-1. Stream 기초

 이제부터는 자바 8에 추가된 Stream에 대해서 알아보자.

 

자바에서 다량의 데이터를 가지고 작업을 해야 할 때 사용한 컬렉션으로는 List, Map, Set 등이 있다.

이 컬렉션들은 데이터를 수집해서 저장하는데 유리하기 때문에 저장된 데이터를 이용해 연산을 할 때에는 List, Map, Set 별로 다른 메소드를 이용해야 한다.

 

자바 8에 추가된 스트림은 컬렉션에 수집된 다량의 데이터를 연산, 조작할 때 좀 더 편리하고 가독성 있게 처리할 수 있도록 해준다.

 

그런데, 1.8버전 이전에도 스트림이라는 개념이 있었는데, 그것은 주로 입출력과 관련된 스트림이었고 1.8 이후의 스트림은 주로 처리나 연산에 관련된 것이므로 같은 용어, 다른 의미라고 보면 된다.

 

그럼 Stream이 무엇인지 공부해 보자.

 

Stream의 사전적 의미는 "개울"이라는 명사적 의미와 "흐른다"는 동사적 의미가 있다.

시냇물이 졸졸 흘러간다는 것인데, 자바에서의 스트림은 당연히 데이터의 흐름이다.

 

Java 8에 추가된 Stream API를 이용하면 다양한 데이터 소스로부터 표준화된 방법으로 데이터를 처리할 수 있으며

이것은 데이터를 다루는데 보다 간결하고 가독성 있는 처리를 가능하게 한다.

 

아래의 코드를 보자. 이 코드는 컬렉션에 저장된 데이터 전체를 순회하는 Java 8 이전의 방식이다.

 

StreamTest1.java

import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class StreamTest1 {
    public static void main(String[] args) {
        List<String> list = List.of("서울", "대전", "대구", "부산", "찍고..");
        
        Iterator<String> iter = list.iterator();
        while(iter.hasNext()) 
            System.out.println(iter.next());
        
        Set<String> set = Set.of("아메리카노", "카푸치노", "카페라떼", "콜드브루", "에스프레소");
        
        Iterator<String> iter2 = set.iterator();
        while(iter2.hasNext()) 
            System.out.println(iter2.next());        
    }
}

 

실행결과

서울 대전 대구 부산 찍고..
에스프레소 카페라떼 아메리카노 카푸치노 콜드브루

 

이 중 List를 순회하여 출력하는 코드를 보면 리스트로부터 Iterator 객체를 얻어 hasNext() 메소드를 통해 소비할 데이터가 남아있는지 여부를 확인 후 true가 반환되면 출력하는 코드이다.

 

이 코드를 스트림으로 변경하면

 

list.stream().forEach(System.out::println);
 

이렇게 한 줄로 표현할 수 있다. 물론 Set도 마찬가지이다.

 

코드를 통해 확인해 보았듯이, 코드는 짧고 간결해지지만 스트림(Stream)을 사용하려면 이전에 공부한 Lambda를 잘 다루는 것이 중요하다는 것을 알 수 있다.

 

8-2. Stream의 특징

  • 데이터를 수집하여 저장하는 것보다 연산에 특화되어 있다.
  • 원본 데이터를 변경하지는 못하고, 자료를 읽어 연산하는데 집중한다.
  • 데이터에 연산 작업이 많다면 기존의 방식보다는 스트림으로 전환하는 것이 유리하다.

 

8-3. Stream의 생성

컬렉션 데이터를 스트림으로 생성하기 위해 사용되는 가장 기본적인 메소드는

Stream<T> stream()

 

이 있고, 각 컬렉션이나 데이터에 따라 여러 가지 방법의 스트림 생성 방식이 있다.

 

※ ListSet 데이터의 Stream 생성의 예 (문자열을 가지고 있는 컬렉션의 경우)

Stream<String> stream1 = list.stream();

Stream< String> stream2 = set.stream();

 

이 밖에 스트림을 생성하는 여러가지 방법은 코드를 통해 하나씩 살펴보도록 하자.

 

8-4. Stream 연산

위에서도 언급했듯이 스트림을 생성하거나 데이터를 저장하는 것보다는 연산이 중요하다.

스트림의 연산은 중간연산최종연산으로 나눌 수 있는데, 중간 연산은 말 그대로 스트림을 이용해 처리하고자 하는 연산이다.

 

중간연산은 메소드가 반환하는 연산 처리된 Stream 객체를 받아 메소드 체이닝을 통해 계속해서 다시 연속적인 연산이 가능하다.

 

최종 연산은 중간연산을 통해 처리된 연산을 마무리하는 연산이다.

 

중간연산과 최종 연산을 하는 메소드는 어떤 것들이 있는지 알아보자

 

중간연산 메소드 1

반환타입 메소드 설명
Stream<T> distinct() 데이터의 중복을 제거
Stream<T> filter(Predicate<T> p) Predicate Lambda식을 전달하여 그 조건에 맞는 데이터를 필터링
Stream<T> limit(long size) 스트림을 전달인자의 값에 맞게 잘라낸다
Stream<T> skip(long n) 스트림의 데이터를 건너뛴다.
Stream<T> peek(Consumer<T> c) 스트림의 데이터를 peek한다. (?)
Stream<T> sorted() 스트림 데이터를 정렬한다.
Stream<T> sorted(Comparator<T> f) 스트림 데이터를 정렬한다.

 

 

StreamOperTest1.java

import java.util.Arrays;
import java.util.stream.IntStream;

public class StreamOperTest1 {

    public static void main(String[] args) {
       int[] intAry = {7, 5, 2, 6, 1, 5, 4, 10, 7, 9, 1, 4, 1};
       
       // 1) IntStream은 Stream 타입의 한 종류로 Integer 타입을 다루는 Stream이다.
       // 정수값을 가지는 배열을 Stream으로 변환한다.
       IntStream sInt = Arrays.stream(intAry);
       
       // 2) IntStream 데이터에서 중복을 제거하는 중간 연산을 수행한다.
       sInt = sInt.distinct();
       
       // 3) 최종연산인 forEach()를 통해 중복이 제거된 데이터를 출력한다.
       sInt.forEach(System.out::println);
       
       /* 위의 코드는 이해를 쉽게 하도록 3단계에 걸쳐 나눠서 코딩하였지만, 
        * 아래와 같이 하나로 처리할 수 있다 */
       Arrays.stream(intAry).distinct().forEach(System.out::println);
    }
}

 

스트림을 이용하여 연산을 할 때에는

먼저 스트림을 생성하고 → 중간연산을 수행(여러 번 가능) 한 후 → 최종연산으로 마무리!!!

 

 


아래 제시된 코드는 정수 배열로부터 스트림을 생성하고, 중간 연산을 거쳐 출력하는 최종연산까지 진행하는 코드이다.

비슷한 예로 문자열이 포함된 스트림을 생성하고 특정 단어가 포함된 문자열을 필터링 한 후 정렬하여 출력한다.

 

예에서 보듯이 중간연산의 결과로 반환하는 타입은 다시 스트림이기 때문에 여러 중간연산을 처리할 수 있다.

그리고 마무리로 최종 연산에 해당하는 forEach() 메소드를 이용하여 출력한다.

 

StreamOperTest2.java

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamOperTest2 {

    public static void main(String[] args) {
        int[] intAry = {7, 5, 2, 6, 1, 5, 4, 10, 7, 9, 1, 4, 1};

        // 1) 정수값을 가지는 배열을 Stream으로 변환한다.
        IntStream sInt = Arrays.stream(intAry);

        // 2) 짝수값만 추출하는 중간연산
        sInt = sInt.filter(x -> x%2 == 0 ? true: false);

        // 3) 중복을 제거하는 중간연산
        sInt = sInt.distinct();

        // 4) 중복이 제거된 짝수값만을 출력하는 최종연산
        sInt.forEach(System.out::println);

        // 5) 위의 모든 작업을 한 번에...
        Arrays.stream(intAry)
              .filter(x -> x%2 == 0 ? true: false)
              .distinct()
              .forEach(System.out::println);

        // 6) 필요한 중간연산 추가: 오름차순으로 정렬을 하는 중간연산 기능 추가됨
        Arrays.stream(intAry)
            .filter(x -> x%2 == 0 ? true: false)
            .distinct()
            .sorted()
            .forEach(System.out::println);
            
        //-------------------------
        // 1) 여러 개의 문자열을 Stream으로 변환한다.
        Stream<String> stream = List.of("순대국밥", "설렁탕", "양푼 비빔밥", "돌솥비빔밥", "비빔국수", "부대찌게", "달걀 볶음밥").stream();

        // 2) 문자열 내에 "밥"이 포함된 데이터만 추출하는 중간연산
        stream = stream.filter(x -> x.contains("밥"));

        // 3) 문자열을 정렬 중간연산
        stream = stream.sorted((x, y) -> x.compareTo(y));

        // 4) 최종연산
        stream.forEach(System.out::println);

        // 5) 위 모든 작업을 한 번에 처리 
        stream.filter(x -> x.contains("밥"))
              .sorted((x, y) -> x.compareTo(y))
              .forEach(System.out::println);

    }
}

 

간단하게 스트림의 개념과 사용방법에 대해서 알아보았다. 

 

다음에는 스트림의 중간연산과 최종 연산을 더 추가적으로 알아보도록 하자.

 


이전 글: [Java] 7. 메소드 참조 (::)

다음 글: [Java] 9. 다양한 Stream의 생성

관련글 더보기

댓글 영역