Java

[Java] Stream

DH_0518 2024. 4. 1. 00:46

스트림(Stream)은 Java 8에서 컬랙션과 배열을 반복적으로 처리할 때 코드의 가독성을 향상시키고 병렬 처리를 쉽게 하기 위해 새로 도입된 api이다. 우리는 데이터를 처리할 때, 알고리즘 로직을 작성하기 보다는 어떤 작업을 원하는지 '선언형(Declarative)'으로 작성함으로써 보다 직관적인 코드를 작성할 수 있다.

 

 

 

 

 

선언형과 명령형

 

 

선언형과 명령형을 비교하여 어떤 것이 다른지 직접 코드로 비교해보자

먼저 우리가 일반적으로 사용하는 명령형 코드이다

public class Example {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> evenNumbers = new ArrayList<>();

        // 명령형 방식: 반복문을 사용하여 짝수만 추출
        for (Integer number : numbers) {
            if (number % 2 == 0) {
                evenNumbers.add(number);
            }
        }

        System.out.println("Even numbers: " + evenNumbers);
    }
}

 

 

명령형은 (1,2,3,4,5)를 원소로 가진 리스트에서 짝수만 추출하기 위해 반복문(for)을 사용하여 리스트의 각 원소들을 비교하였고, 원소마다 조건문(if)을 사용하여 조건을 만족하면 리스트에 추가하여 짝수만 추출하였다.

 

즉, 어떤 조건을 만족하는 대상(what)에 따라, 어떻게(how) 처리할지를 직접 작성해주었다.

 

이 코드를 선언형으로 바꿔보자

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Example {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 선언형 방식: 스트림과 중간 연산(filter) 및 최종 연산(collect)을 사용하여 짝수만 필터링
        List<Integer> evenNumbers = numbers.stream()
                            .filter(n -> n % 2 == 0)
                            .collect(Collectors.toList());

        System.out.println("Even numbers: " + evenNumbers);
    }
}

 

반면에 선언형은, 중간 연산과 최종 연산을 통해서 간단하게 필터링 조건과 마무리 형태만을 나타내 주었다.

데이터를 가공하기 위해서 반복문을 써야하고, 조건문을 걸어주며, 조건을 만족했을 때 어떻게 처리해야 하는지에 대한 구현을 구체적으로 명시하지 않고 추상화하여 표현 한 것이다.

 

이를 통해 선언형이 어떻게 더 간결하고 가독성 좋은 코드를 작성할 수 있게 해주는지 비교가 되었을 것이다.

 

 

 

 

Stream

 

 

그럼 이제 본격적으로 스트림에 대해서 알아보자

 

스트림(Stream)의 특징

  • 데이터를 순차적, 혹은 병렬적으로 실행할 수 있다
  • Collection과 Stream의 가장 큰 차이점은 '데이터를 계산하는 시점'이다
    • 컬렉션의 모든 요소는, 컬렉션에 추가하기 전에 계산되어야 한다 (여러 연산이 중첩된 경우, 각 연산 이전에 모두 계산되어야 한다)
    • 스트림은 요청할때만 요소를 계산한다(지연 연산을 뜻한다)
  • 외부 반복을 하는 Collection과 다르게 내부 반복을 통해 효율적으로 계산을 할 수 있다
    • 외부 반복
      - 개발자가 반복문(for, while)과 조건문(if) 등을 사용하여 직접 반복을 제어하는 방식
    • 내부 반복
      - 반복문으로 명시하지 않아도 라이브러리나 API에 의해 자동으로 반복이 이루어지기에, 개발자가 반복을 직접 제어하지 않는다
  • 중간 연산과 최종 연산으로 구성되어있다
  • 중간 연산에서 여러 개의 조건이 중첩되었을 때, 모든 조건을 확인하기 이전에 값이 결정이 나면 더 이상 불필요한 실행을 하지 않는 쇼트 서킷(short-circuit)을 통해 실행 속도을 향상시킨다
  • 중간 연산이 최종 연산을 만나기 전까지 실제로 연산이 이루어지지 않는 지연 연산(Lazy Evaluation)을 통해 불필요한 연산을 줄여서 실행 속도를 향상시킨다
    • 각 중간 연산별로 결과를 구해놓는 것이 아니라, 중간 연산이 조건문이 되어서 각 연산별로 이루어진다고 생각하면 된다
    • 다음을 참고하자 : https://dororongju.tistory.com/137
 

[자바] Lazy Evaluation 이란?

Lazy Evaluation 직역하면 "게으른 연산"인데, 연산을 불필요한 연산을 피하기 위해 연산을 지연시키는 것을 말합니다. 예를 들어 보겠습니다. 아래의 코드는 1~10까지의 정수를 갖는 List에서 6보다 작

dororongju.tistory.com

 

 

중간 연산(intermediate operations)

: 모두 스트림을 반환하고, 최종 연산을 만났을 때 연산이 이루어진다

  • filter(Predicate) : Predicate를 인자로 받아, 결과가 true인 요소만 포함하는 스트림을 반환
  • distinct() : 중복된 요소를 제거
  • limit(n) : 주어진 사이즈 이하 크기를 갖는 스트림을 반환
  • skip(n) : 처음 요소 n개를 제외한 스트림을 반환
  • map(Function) : 각 요소를 주어진 매핑 함수의 result로 구성된 스트림을 반환
  • flatMap() : 각 요소를 스트림으로 매핑하고, 평면화된 하나의 스트림으로 반환
  • sorted() : 요소들을 정렬

 

최종 연산(terminal operations)

  • 조건 검사
    • (boolean) allMatch(Predicate) : 모든 스트림 요소가 Predicate와 일치하는지를 반환
    • (boolean) anyMatch(Predicate) : 하나라도 일치하는 요소가 있다면 true를 반환
    • (boolean) noneMatch(Predicate) : 매치되는 요소가 하나도 없다면 true를 반환
  • 연산
    • reduce(binary operator, initial value) : 모든 스트림 요소를 하나의 값으로 처리해서 반환, 초기값은 생략 가능
    • (Long) count : 스트림 요소 개수를 반환
    • (void) forEach() : 스트림의 각 요소에 대해 주어진 작업을 수행
  • 검색
    • (Optional) findAny() : 현재 스트림에서 임의의 요소를 반환
    • (Optional) findFirst() : 스트림의 첫번째 요소를 반환
    • (Optional<T>) min(comparator) : 스트림에서 가장 작은 요소를 반환
    • (Optional<T>) max(comparator) : 스트림에서 가장 큰 요소를 반환
  • 컬렉션
    • (Collection) collect() : 스트림을 reduce하여 list, map, 정수 형식 컬렉션을 반환

 

// map()
List<String> names = Arrays.asList("Sehoon", "Songwoo", "Chan", "Youngsuk", "Dajung");
names.stream()
    .map(name -> name.toUpperCase())
    .forEach(name -> System.out.println(name));
    

// filter()
List<String> startsWithN = names.stream()
    .filter(name -> name.startsWith("S"))
    .collect(Collectors.toList());


// reduce()
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));


// collect()
System.out.println(names.stream()
                   .map(String::toUpperCase)
                   .collect(Collectors.joining(", ")));

 

 

 

 

 

 

 

 

 

 

 

Reference

 

JAVA Stream | 👨🏻‍💻 Tech Interview

JAVA Stream Java 8버전 이상부터는 Stream API를 지원한다 자바에서도 8버전 이상부터 람다를 사용한 함수형 프로그래밍이 가능해졌다. 기존에 존재하던 Collection과 Stream은 무슨 차이가 있을까? 바로 **'

gyoogle.dev

 

자바 스트림 설명부터 사용하는 이유 파헤쳐보기 #JAVA #스트림

자바 스트림 설명부터 사용하는 이유 파헤쳐보기 #JAVA #스트림 안녕하세요? 장장스입니다. 오늘은 자바 스트림에 대해서 정리해 보겠습니다. 스트림(Stream)이란 무엇인가? 스트림(Stream)은 자바 8 A

zangzangs.tistory.com

 

[자바] Lazy Evaluation 이란?

Lazy Evaluation 직역하면 "게으른 연산"인데, 연산을 불필요한 연산을 피하기 위해 연산을 지연시키는 것을 말합니다. 예를 들어 보겠습니다. 아래의 코드는 1~10까지의 정수를 갖는 List에서 6보다 작

dororongju.tistory.com