개발 공부

[Java 8] Stream API 기본 문법

jong9810 2023. 9. 28. 16:41

구글링을 하다 보면 반복문을 쓰는 코드도 있지만 Stream을 사용한 코드도 심심치 않게 보이는 것 같습니다. 볼 때마다 대충 어떤 기능인지는 알았지만 정확한 역할을 잘 몰랐기 때문에 이번 기회에 글로 정리해보려고 합니다.

Stream이란?

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다. 스트림이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다. 즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 합니다. 이 스트림은 Java 8부터 추가된 스트림 API라는 개념과 별개의 개념입니다.

 

스트림 API는 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된데이터를 읽고 쓰기 위한 공통된 방법을 제공합니다. 자바에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 이용합니다. 이렇게 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성해야 합니다. 하지만 이렇게 작성된 코드는 길이가 너무 길고 가독성도 떨어지며, 코드의 재사용이 거의 불가능합니다. 즉, 데이터베이스의 쿼리와 같이 정형화된 처리 패턴을 가지지 못했기에 데이터마다 다른 방법으로 접근해야만 했습니다. 이러한 문제점을 극복하기 위해서 JAVA SE 8부터 스트림 API를 도입했다.

 

한 마디로, "stream(stream api)은 컬렉션 배열 등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(람다식)를 적용하여 반복적으로 처리할 수 있도록 해주는 기능"이다.

 

스트림의 특징

  • 스트림을 생성한다고 해도 clone처럼 데이터 소스를 변경하지는 않는다.
  • 스트림은 일회용이다(재사용 불가).
  • 스트림은 작업을 내부 반복으로 처리한다(작업이 간결).
  • 스트림은 병렬처리를 하기 편리하다(병렬처리가 항상 더 빠른 결과를 얻게 해주는 것은 아님).

Stream 생성

Stream은 Collection, List, Set, Array 등의 자료구조로부터 생성될 수 있습니다.

// 1) Collection으로부터 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
 
// 2) Array로부터 생성
String[] arr = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);

// 3) Stream.of() 함수 이용
Stream<String> stream = Stream.of("a", "b", "c");

Stream 중간연산

Stream에 대해서 중간 연산을 수행할 수 있습니다. 중간 연산은 Stream을 반환하기 때문에, 여러 개의 중간 연산을 연결하여 사용할 수 있습니다. 최종 연산을 하기 전까지의 중간 연산들은 계획일 분이고 실제 실행은 최종 연산시 일괄 실행된다.

// 1) filter() : 조건에 맞는 요소만을 선택하여 Stream으로 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
Stream<String> filteredStream = stream.filter(str -> str.length() > 5);

// 2) map() : 각 요소에 대해 매핑 함수를 적용하여 새로운 Stream을 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry");
Stream<String> mappedStream = stream.map(str -> str.length());

// 3) flatMap() : 각 요소에 대해 매핑 함수를 적용하여 새로운 Stream을 생성하고, 이를 하나의 Stream으로
//                병합하여 반환합니다.
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4));
Stream<Integer> flatMappedStream = stream.flatMap(list -> list.stream());

// 4) sorted() : 요소를 정렬하여 Stream으로 반환합니다.
Stream<String> stream = Stream.of("banana", "apple", "durian", "cherry");
Stream<String> sortedStream = stream.sorted();

// 5) distinct() : 중복된 요소를 제거한 Stream을 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "apple", "cherry");
Stream<String> distinctedStream = stream.distinct();

// 6) limit() : 처리할 요소의 개수를 제한하여 Stream을 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
Stream<String> limitedStream = stream.limit(2);

// 7) skip() : 처리할 요소의 개수를 건너뛰고, 나머지 요소로 구성된 Stream을 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
Stream<String> skippedStream = stream.skip(2);

Stream 최종연산

Stream에 대해서 최종 연산을 수행할 수 잇습니다. 최종 연산은 Stream을 반환하지 않고 최종 결과를 반환합니다. Stream의 최종 연산은 딱 한 번만 수행 가능합니다.

// 1) forEach() : 각 요소에 대해 작업을 수행합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
stream.forEach(str -> System.out.println(str));

// 2) toArray() : Stream 요소를 배열로 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
String[] arr = stream.toArray(String[]::new);

// 3) reduce() : Stream 요소를 하나로 줄여 하나의 결과를 반환합니다.
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
int sum = stream.reduce(0, (acc, val) -> acc + val);

// 4) collect() : Stream 요소를 수집하여 새로운 Collection, List, Set 등으로 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
List<String> list = stream.collect(Collectors.toList());

// 5) count() : Stream의 요소 개수를 반환합니다.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
long count = stream.count();

// 6) anyMatch() : 하나 이상의 요소가 조건에 맞는지 검사하여 결과를 반환합니다.
// 요소 중 하나라도 조건에 해당하면 true, 모든 요소가 조건에 해당하지 않으면 false.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
boolean result = stream.anyMatch(str -> str.startsWith("a"));

// 7) allMatch() : 모든 요소가 조건에 맞는지 검사하여 결과를 반환합니다.
// 모든 요소가 조건에 해당하면 true, 하나라도 조건에 해당하지 않으면 false.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
boolean result = stream.allMatch(str -> str.length() > 3);

// 8) noneMatch() : 모든 요소가 조건에 맞지 않는지 검사하여 결과를 반환합니다.
// 모든 요소가 조건에 해당하지 않으면 true, 하나라도 조건에 해당하면 false.
Stream<String> stream = Stream.of("apple", "banana", "cherry", "durian");
boolean result = stream.noneMatch(str -> str.startsWith("z"));

참고한 블로그

https://jeong-pro.tistory.com/165

https://sm-code.tistory.com/entry/Java-8-Stream-API-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-%EB%B0%8F-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%A0%9C

https://velog.io/@chamominedev/%EC%8A%A4%ED%8A%B8%EB%A6%BCstream%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

 

다음으로

Stream이 뭔지와 기본 문법, 함수에 대해 정리해보았습니다. Stream을 검색하다가 추가로 람다식과 Optional에 대한 정리글도 작성해보면 좋을 것 같다는 생각이 들었습니다. Stream은 앞으로 개발을 하면서 더 알게되는 내용이 있으면 더 추가하는 걸로 하고, 다음으로는 자바 람다식에 대해 정리해보겠습니다.

'개발 공부' 카테고리의 다른 글

[Front-end] React 이벤트 핸들링  (1) 2023.09.30
[Front-end] React 훅  (0) 2023.09.30
[Front-end] React State와 생명주기  (1) 2023.09.26
[Front-end] React 컴포넌트와 Props  (0) 2023.09.26
[Front-end] React 엘리먼트 렌더링  (0) 2023.09.25