반응형
☕️ 스트림 API(Stream API) 란?
Stream API는 Java 8에서 추가된 기능으로써 배열 또는 컬렉션 등을 함수형 프로그래밍 기법으로 처리할 수 있도록 도와주는 API이다.
☕️ 스트림 API(Stream API)의 특징
- 원본 데이터를 변경하지 않는다.
원본 데이터를 토대로 Stream을 생성하고 가공하기 때문에 정렬, 필터링 등의 가공이 있어도 원본 데이터는 훼손되지 않는다. - 컬렉션과 달리 재사용이 불가하여 단 1번만 사용할 수 있다.
Stream의 연산이 모두 끝나면 재사용이 불가능하다. 만일 재사용을 하게 될 경우, IllegalStateException이 발생하게 된다. 그러므로 Stream 연산이 다시 필요한 경우에는 Stream을 새로 생성해줘야 한다. - 내부 반복을 통해 작업을 처리한다.
Stream API를 사용하면 코드가 간결해지는 이유 중 하나는 '내부 반복' 때문이다. 기존에는 반복문을 사용하기 위해서 for, while 등과 같은 문법을 사용했지만 Stream에서는 그러한 반복 문법을 메서드 내부에 숨기고 있기 때문에 보다 간결한 코드의 작성이 가능하다. - parallelStream() 메서드를 통한 병렬처리를 지원한다.
parallelStream() 메서드를 사용해 병렬처리를 쉽게 구현할 수 있다.
☕️ 스트림 API(Stream API)의 연산
Stream API의 연산 순서는 '생성 > 중간연산 > 최종연산'이다.
각 각의 연산에 대하여 사용할 수 있는 메서드는 다음과 같다.
- 생성
- 중간 연산(Intermediate Operation)
- filter() : 스트림 요소마다 조건문을 만족하는 요소로 구성된 스트림을 반환
- distinct() : 스트림 요소들의 중복을 제거한 스트림을 반환
- map() : 스트림의 각 요소마다 수행할 연산을 구현할 때 사용
- flatMap() : 스트림 요소를 새로운 요소로 대체한 스트림을 생성
- limit() : 스트림의 시작 요소로 부터 인자로 전달된 인덱스까지의 요소를 추출해 새로운 스트림을 생성
- skip() : 스트림의 시작 요소로 부터 인자로 전달된 인덱스 까지를 제외하고 새로운 스트림을 생성
- sorted() : 스트림 요소를 정렬하는 method로 기본적으로 오름차순으로 정렬
- peek() : 결과 스트림의 요소를 사용해 추가로 동작을 수행 - 최종 연산(Terminal Operation)
- forEach() : 트림의 요소들을 순환하면서 반복해서 처리해야 하는 경우 사용
- reduce() : map과 비슷하게 동작하지만 개별연산이 아니라 누적연산이 이루어진다는 차이 존재
- findFirst() : 스트림에서 지정한 첫 번째 요소를 찾는 메서드
- findAny() : 스트림에서 지정한 첫 번째 요소를 찾는 메서드. parallelStream()와 함께 사용
- anyMatch() : 스트림의 요소 중 특정 조건을 만족하는 요소가 하나라도 있는지 검사
- allMatch() : 스트림의 요소 중 특정 조건을 모두 만족하는지 검사
- noneMatch() : 스트림의 요소 중 특정 조건을 만족하는 요소가 하나도 없는지 검사
- count() : 스트림의 원소들로부터 전체 개수 구하기 위한 메서드
- min() / max() : 스트림의 원소들로부터 최솟값 / 최댓값을 구하기 위한 메서드
- sum() / average() : 스트림 원소들의 합계 / 평균을 구하는 메서드
- collect() : 스트림의 결과를 모으기 위한 메서드
중간 연산(Intermediate Operation) 예제 코드
더보기
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class IntermediateOperation {
public static void main(String[] args) {
/*
* 중개 연산(Intermediate Operation)
- 스트림 필터링 : filter(), distinct()
- 스트림 변환 : map(), flatMap()
- 스트림 제한 : limit(), skip()
- 스트림 정렬 : sorted()
- 스트림 연산 결과 확인 : peek()
*/
List<String> list = Arrays.asList("java", "java", "c", "c++", "c++", "javascript", "css", "html", "go", "python");
Stream<String> originStream = null;
System.out.println("\n\n--- filter ---");
originStream = list.stream();
Stream<String> filterStream = originStream.filter(s -> 'c' == s.charAt(0));
filterStream.forEach(s -> System.out.print(s + " ")); // c c++ c++ css
System.out.println("\n\n--- distinct ---");
originStream = list.stream();
Stream<String> distinctStream = originStream.distinct();
distinctStream.forEach(s -> System.out.print(s + " ")); // java c c++ javascript css html go python
System.out.println("\n\n--- map ---");
originStream = list.stream();
Stream<String> mapStream = originStream.map(s -> s.toUpperCase());
mapStream.forEach(s -> System.out.print(s + " ")); // JAVA JAVA C C++ C++ JAVASCRIPT CSS HTML GO PYTHON
System.out.println("\n\n--- flatMap ---");
/*
flatMap
- 스트림의 형태가 배열과 같을 때, 모든 원소를 단일 원소 스트림으로 반환
- map과의 차이점 : map 메서드는 스트림의 스트림을 반환하는 반면에 flatMap 메서드는 스트림을 반환한다.
따라서 출력을 했을 때, 아래와 같은 차이를 지닌다.
*/
String[][] namesArray = new String[][]{
{"kim", "choi"}, {"lee", "park"}};
// map #1
Arrays.stream(namesArray)
.map(inner -> Arrays.stream(inner))
.forEach(s -> System.out.print(s + " ")); // java.util.stream.ReferencePipeline$Head@5caf905d java.util.stream.ReferencePipeline$Head@27716f4
// map #2
Arrays.stream(namesArray)
.map(inner -> Arrays.stream(inner))
.forEach(names -> names.forEach(s -> System.out.print(s + " "))); // kim choi lee park
// flatMap
Arrays.stream(namesArray)
.flatMap(inner -> Arrays.stream(inner))
.forEach(s -> System.out.print(s + " ")); // kim choi lee park
System.out.println("\n\n--- limit ---");
originStream = list.stream();
Stream<String> limitStream = originStream.limit(3); // 3개까지 추출
limitStream.forEach(s -> System.out.print(s + " ")); // java java c
System.out.println("\n\n--- skip ---");
originStream = list.stream();
Stream<String> skipStream = originStream.skip(3); // 3번째까지 생략하고 추출
skipStream.forEach(s -> System.out.print(s + " ")); // c++ c++ javascript css html go python
System.out.println("\n\n--- sorted ---");
originStream = list.stream();
Stream<String> sortedStream = originStream.sorted();
sortedStream.forEach(s -> System.out.print(s + " ")); // c c++ c++ css go html java java javascript python
System.out.println("\n\n--- peek ---");
originStream = list.stream();
Stream<String> peekStream = originStream.sorted();
peekStream
.map(s -> s.toUpperCase())
.peek(s -> System.out.println("1. " + s ))
.distinct()
.peek(s -> System.out.println("2. " + s))
.forEach(s -> System.out.println("3. " + s));
/*
- peek : 스트림의 요소를 소비하지 않고 확인할 수 있다. (중간에 데이터 처리 과정을 볼 수 있다.)
- forEach와의 차이점
: peek = 중간연산(스트림을 소비하지 않는다)
: forEach = 최종연산(스트림을 소비한다)
- 실행 결과
1. C
2. C
3. C
1. C++
2. C++
3. C++
1. C++
1. CSS
2. CSS
3. CSS
1. GO
2. GO
3. GO
1. HTML
2. HTML
3. HTML
1. JAVA
2. JAVA
3. JAVA
1. JAVA
1. JAVASCRIPT
2. JAVASCRIPT
3. JAVASCRIPT
1. PYTHON
2. PYTHON
3. PYTHON
*/
}
}
최종 연산(Terminal Operation) 예제 코드
더보기
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TerminalOperation {
public static void main(String[] args) {
/*
* 최종 연산(Terminal Operation)
- 출력 : forEach(), forEachOrdered()
- 소모 : reduce()
- 검색 : findFirst(), findAny()
- 검사 : anyMatch(), allMatch(), noneMatch()
- 통계 : count(), min(), max()
- 연산 : sum(), average()
- 수집 : collect()
*/
System.out.println("\n\n--- forEach ---");
// 원소를 출력한다.
Arrays.asList(5, 6, 8, 1, 9, 1, 3, 8, 7)
.stream()
.forEach(s -> System.out.print(s + " ")); // 5 6 8 1 9 1 3 8 7
System.out.println("\n\n--- forEach(parallel) ---");
Arrays.asList(5, 6, 8, 1, 9, 1, 3, 8, 7)
.stream()
.parallel() // 병렬처리
.forEach(s -> System.out.print(s + " ")); // 1 9 8 7 3 8 1 6 5 : 병렬처리로 인하여 순서가 보장되지 않는다.
System.out.println("\n\n--- forEachOrdered(parallel) ---");
Arrays.asList(5, 6, 8, 1, 9, 1, 3, 8, 7)
.stream()
.parallel() // 병렬처리
.forEachOrdered(s -> System.out.print(s + " ")); // 5 6 8 1 9 1 3 8 7 : forEachOrdered를 사용하면 순서가 보장된다.
System.out.println("\n\n--- reduce ---");
// 두 개의 인자 n, n+1을 가질 때, 연산결과는 n이 되고 다시 다음 요소와 연산을 하게된다.
// 즉 1, 2번째 요소를 연산하고 그 연산 결과를 3번째 요소와 다시 연산하는 방식이다.
System.out.println(
Arrays.asList(1, 2, 3, 4)
.stream()
.reduce((a, b) -> (a + b)).get()
); // 10
System.out.println("\n\n--- findFirst ---");
// findFirst()는 보통 filter()와 함께 사용되며, 스트림에서 지정된 첫 번째 요소를 찾는 함수이다.
Arrays.asList("c", "c++", "java", "javascript", "go", "python")
.stream()
.filter(s -> s.startsWith("j"))
.findFirst()
.ifPresent(System.out::print); // java
System.out.println("\n\n--- findAny ---");
// findAny()는 parallelStream()에서 병렬 처리 시, 가장 먼저 발견된 요소를 찾는 메서드로 결과는 스트림 원소의 정렬 순서와 상관 없다.
Arrays.asList("c", "c++", "java", "javascript", "go", "python")
.parallelStream()
.filter(s -> s.startsWith("j"))
.findAny()
.ifPresent(System.out::print); // java 또는 javascript
System.out.println("\n\n--- anyMatch ---");
// 원소 중 일부라도 일치하는게 있는지 확인한다.
System.out.println(
Arrays.asList("c", "c++", "java", "javascript", "go", "python")
.stream()
.anyMatch(s -> s.startsWith("j"))
); // true
System.out.println("\n\n--- allMatch ---");
// 원소 전체가 일치하는지 확인한다.
System.out.println(
Arrays.asList("c", "c++", "java", "javascript", "go", "python")
.stream()
.allMatch(s -> s.startsWith("j"))
); // false
System.out.println("\n\n--- noneMatch ---");
// 원소가 모두 일치하지 않는지 확인한다.
System.out.println(
Arrays.asList("c", "c++", "java", "javascript", "go", "python")
.stream()
.noneMatch(s -> s.startsWith("a"))
); // true
System.out.println("\n\n--- count ---");
// 원소의 갯수를 구한다.
System.out.println(
Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.count()
); // 6
System.out.println("\n\n--- min ---");
// 원소 중 최솟값을 구한다.
System.out.println(
Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.min(Integer::compare)
.get()
); // 1
System.out.println("\n\n--- max ---");
// 원소 중 최댓값을 구한다.
System.out.println(
Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.max(Integer::compare)
.get()
); // 8
System.out.println("\n\n--- sum ---");
// 원소의 합을 구한다.
System.out.println(
Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.mapToInt(Integer::intValue)
.sum()
); // 23
System.out.println("\n\n--- average ---");
// 원소의 평균을 구한다.
System.out.println(
Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.mapToInt(Integer::intValue)
.average()
); // 3.833...
System.out.println("\n\n--- collect ---");
// 스트림을 배열이나 컬렉션으로 변환한다.
List<Integer> intList = Arrays.asList(5, 8, 1, 2, 4, 3)
.stream()
.collect(Collectors.toList());
System.out.println(intList); // [5, 8, 1, 2, 4, 3]
}
}
References.
1. 망나니개발자 - [Java] Stream API에 대한 이해 - (1/5)
2. 썸머워즈 - [JAVA] 자바_스트림 API (Stream API) 사용하기
3. Contributor9 - [Java] Stream API -1 이해하기: 용어 및 Stream 생성
반응형
'Java' 카테고리의 다른 글
[Java] java.time 패키지 (0) | 2023.09.04 |
---|---|
[Java] Optional (0) | 2023.09.02 |
[Java] 메서드 참조(Method Reference) (0) | 2023.08.16 |
[Java] 함수형 인터페이스(Functional Interface) (0) | 2023.08.14 |
[Java] Set과 equals() 및 hashCode()의 관계 (0) | 2023.08.13 |