스트림(Stream) 이란?
스트림은 배열, 컬렉션의 저장요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다. 스트림을 사용하면 List, Set, Map 등 다양한 데이터 소스로부터 스트림을 만들 수 있고 이를 표준화된 방법으로 다룰 수 있다.
스트림 도입 배경
데이터를 효과적으로 다루고 원하는 모양대로 데이터로 가공하기 위해 for문과 Iterator를 활용한다. 다만 이런 방식들은 두 가지 큰 문제점이 있는데 첫 번째로 코드가 길고 복잡해질 수 있다. 아래 예를 확인해보자.
- Iterator를 사용한 반복처리
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class PrintNumberOperator {
public static void main(String[] args) {
// 각 숫자를 배열화
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// Iterator 생성
Iterator<Integer> it = list.iterator();
// 리스트를 순회하며 값 출력
while (it.hasNext()) {
int num = it.next();
System.out.print(num);
}
}
}
//출력값
12345
위의 코드 예제는 List 컬렉션에서 Iterator 를 사용하여 값을 순서대로 출력하는 프로그램의 코드이다.
위의 결과를 출력하는 스트림을 사용한 코드를 작성하면 아래와 같다.
- 스트림을 사용한 반복 처리
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class PrintNumberOperatorByStream {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
}
}
//출력값
12345
스트림을 사용하면 선언형 프로그래밍(Declarative Programming) 방식으로 데이터를 처리할 수 있어 인간 친화적이고 직관적인 코드 작성이 가능하다. 일반적인 알고리즘 문제 해결 등 어떤 주어진 과업을 달성하기 위해 코드 한줄 한줄 규정하는 방식은 명령형 프로그래밍(Imperative Programming) 이라고 한다.
- 명령형 프로그래밍 방식
import java.util.List;
public class ImperativeProgramming {
public static void main(String[] args){
// List에 있는 숫자 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum = 0;
for(int number : numbers){
if(number > 4 && (number % 2 == 0)){
sum += number;
}
}
System.out.println("명령형 프로그래밍을 사용한 합계 : " + sum);
}
}
//출력값
명령형 프로그래밍을 사용한 합계 : 14
- 선언형 프로그래밍 방식
import java.util.List;
public class DeclarativePrograming {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum =
numbers.stream()
.filter(number -> number > 4 && (number % 2 == 0))
.mapToInt(number -> number)
.sum();
System.out.println("선언형 프로그래밍을 사용한 합계 : " + sum);
}
}
//출력값
선언형 프로그래밍을 사용한 합계 : 14
두 번째로 기존 방식의 데이터 처리 방법의 경우 데이터 소스를 각기 다른 방식으로 다뤄야 한다는 불편함이 있다. 예를 들면 어떠한 데이터의 집합을 순차적으로 정렬해야 할 때 배열은 Arrays.sort(), List는 Collections.sort() 로 다르게 사용해야 한다. 이런 문제를 해결하기 위해 도입된자바의 문법 요소가 스트림이다. 스트림을 사용하면 데이터 소스가 무엇이냐에 관계없이 같은 방식으로 데이터를 가공/처리할 수 있다.
아래의 예시를 보자.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamOperator {
public static void main(String[] args) {
// ArrayList
List<String> fruitList = new ArrayList<>();
fruitList.add("바나나 ");
fruitList.add("사과 ");
fruitList.add("오렌지 ");
// 배열
String[] fruitArray = {"바나나 ", "사과 ", "오렌지 "};
// 각각 스트림 생성
Stream<String> ListStream = fruitList.stream();
Stream<String> ArrayStream = Arrays.stream(fruitArray);
// 출력
ListStream.forEach(System.out::print);
ArrayStream.forEach(System.out::print);
}
}
//출력값
바나나 사과 오렌지 바나나 사과 오렌지
스트림을 사용하여 각기 다른 데이터 소스를 동일한 방식으로 처리하고 있다. 문자열을 요소로 가지는 List와 문자열 배열에 각각 스트림을 생성하고 forEach() 메서드를 사용해서 각 요소를 순회하며 출력하고 있다.
forEach() 메서드는 데이터 소스의 각 요소를 순회하면서 람다식 안에 정의된 어떤 명령을 실행하는 데 사용하는 최종 연산자이다.
결론적으로 스트림은 앞서 언급한 기존 방식들의 두 가지 한계를 효과적으로 보완하며 좀 더 간결하고 직관적인 코드 작성을 가능하게 한다.
스트림의 특징
스트림의 핵심적인 특징은 4가지가 있다.
- 스트림 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성될 수 있다.
- 스트림은 원본 데이터 소스를 변경하지 않는다(read-only).
- 스트림은 일회용이다(onetime-only).
- 스트림은 내부 반복자이다.
1. 스트림 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성될 수 있다.
스트림 파이프라인(stream pipeline) 요약된 그림을 봐보자.
스트림 파이프라인은 1) 스트림의 생성, 2) 중간연산, 3) 최종연산 이라는 총 세 단계로 구성되어 있다. 간략하게 흐름을 설명하자면 먼저 배열, 컬렉션, 임의의 수 등 다양한 데이터 소스를 일원화하여 스트림으로 작업하기 위해서는 스트림을 생성해야 한다. 스트림이 생성되고 나면, 최종 처리를 위한 중간 연산을 수행할 수 있다.
여기에는 필터링, 매핑, 정렬 등의 작업이 포함되며 중간연산의 결괴는 또 다른 스트림이기 때문에 계속 연결해서 연산을 수행할 수 있다. 이렇게 연결된 모습이 파이프라인 같다고 해서 스트림 파이프라인 이라고 부른다.
마지막으로, 이렇게 중간 연산이 완료된 스트림을 최종적으로 처리하는 최종 연산을 끝으로 스트림은 닫히고 모든 데이터 처리가 완료된다. 최종 연산의 경우 스트림의 요소를 소모하며 연산하기 때문에 최종적으로 단 한번의 연산만 가능하다. 따라서 최종 연산 후 다시 데이터를 처리하고 싶다면 다시 스트림을 생성해주어야 한다.
위의 그림은 남성과 여성으로 구성된 어떤 회원 컬렉션을 스트림을 사용하여 의도한 데이터로 가공하는 과정을 보여주는 스트림 파이프라인이다. 가장 먼저 스트림을 생성하고, 생성한 스트림에서 중간 연산단계료 성별이 남자인 회원만 필터링 한 후 그중에 나이 요소만 매핑한 후 최종 연산을 통해 남자 회원들의 나이 평균은 구했다.
2. 스트림은 원본 데이터 소스를 변경하지 않는다(read-only).
스트림은 그 원본이 되는 데이터 소스의 데이터들을 변경하지 않는다. 오직 데이터를 읽어올 수 있고, 데이터에 대한 변경과 처리는 생성된 스트림 안에서만 수행된다. 이는 원본 데이터가 스트림에 의해 임의로 변경되거나 데이터 손상이 되는 일을 방지하기 위함이다.
3. 스트림은 일회용이다(onetime-only).
위에서 언급했듯이 스트림은 일회용이다. 다르게 표현하면 스트림이 생성되고 여러 중간 연산을 거쳐서 마지막 연산이 수행되고 난 후 스트림은 닫히고 다시 사용할 수 없다. 추가적인 작업이 필요하다면 다시 스트림을 생성해야 한다.
4. 스트림은 내부 반복자이다.
외부반복자란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 의미한다 인덱스를 사용하는 for문, Iterator 를 사용하는 while 문이 대표적이다.
반면 스트림은 반대로 컬렉션 내부에 데이터 요소 처리방법(람다식)을 주입해서 요소를 반복 처리하는 방식이다.
스트림의 생성
스트림으로 데이터를 처리하기 위해서는 가장 먼저 스트림을 생성해야 한다. 스트림을 생성할 수 있는 데이터 소스는 배열, 컬렉션, 임의의 수 특정 범위의 정수 등 다양한데, 이에 따라 스트림의 생성 방법이 조금씩 차이가 있다.
배열 스트림 생성
- Arrays.stream()
public class StreamCreator {
public static void main(String[] args) {
// 문자열 배열 선언 및 할당
String[] arr = new String[]{"김코딩", "이자바", "박해커"};
// 문자열 스트림 생성
Stream<String> stream = Arrays.stream(arr);
// 출력
stream.forEach(System.out::println);
}
}
// 출력값
김코딩
이자바
박해커
- Stream.of()
import java.util.stream.Stream;
public class StreamCreator {
public static void main(String[] args) {
// 문자열 배열 선언 및 할당
String[] arr = new String[]{"김코딩", "이자바", "박해커"};
// 문자열 스트림 생성
Stream<String> stream = Stream.of(arr);
// 출력
stream.forEach(System.out::println);
}
}
// 출력값
김코딩
이자바
박해커
위 예제들의 출력값을 확인해보면 Array.stream()과 Stream.of() 메서드 모두 같은 값을 출력하고 있다는 사실을 알 수 있다. 따라서 배열로 스트림을 생성할 시에는 둘 중 더 편한 메서드를 선택하여 사용할 수 있다. 추가로 Arrays 클래스에는 int, long, double 과 같은 기본형 배열을 데이터 소스로 스트림을 생성하는 메서드도 있다.
- Arrays 클래스
- IntStream 유용한 기능들
import java.util.Arrays;
import java.util.stream.IntStream;
public class StreamCreator {
public static void main(String[] args) {
// int형 배열로 스트림 생성
int[] intArr = {1,2,3,4,5,6,7};
IntStream intStream = Arrays.stream(intArr);
// 숫자와 관련된 경우 intStream을 사용하는 것을 권장
System.out.println("sum=" + intStream.sum());
// System.out.println("average=" + intStream.average());
}
}
//출력값
sum=28
컬렉션 스트림 생성
컬렉션 타입(List, Set 등)의 경우 컬렉션의 최상위 클래스인 Collection에 정의된 stream() 메서드를 사용하여 스트림을 생성할 수 있다. 따라서 Collection 으로부터 확장된 하위 클래스 List와 Set을 구현한 컬렉션 클래스들은 모두 stream() 메서드를 사용하여 스트림을 생성할 수 있다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreator {
public static void main(String[] args) {
// 요소들을 리스트
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
}
}
//출력값
1234567
위의 예제는 List 타입의 스트림을 생성하는 과정을 보여주고 있다. Arrays 클래스에 static 하게 선언된 asList() 메서드를 사용하여 요소들을 리스트 타입의 참조변수에 할당한 뒤에 stream()메서드를 사용하여 스트림을 생성했다.
임의의 수 스트림 생성
난수를 생성하는 자바의 기본 내장 클래스 Random 클래스 안에는 해당 타입의 난수들을 반환하는 스트림을 생성하는 메서드들이 정의되어있다. 예를 들면 int() 메서드의 경우 int형의 범위 안에 있는 난수들을 무한대로 생성하여 IntStream 타입의 스트림으로 변환한다.
import java.util.Random;
import java.util.stream.IntStream;
public class StreamCreator {
public static void main(String[] args) {
// 난수 생성
IntStream ints = new Random().ints();
ints.forEach(System.out::println);
}
}
int형의 범위에서 출력값이 무한대로 생성된다. 이렇게 스트림의 크기가 정해지지 않은 것을 무한 스트림(infinite stream)이라 부르는데 무한 스트림은 주로 limit()메서드와 함께 사용하거나 매개변수로 스트림의 사이즈를 전달해 그 범위를 제한할 수 있다.
import java.util.Random;
import java.util.stream.IntStream;
public class StreamCreator {
public static void main(String[] args) {
// 스트림 생성의 범위를 5개로 제한
IntStream ints = new Random().ints(5);
IntStream ints = new Random().ints().limit(5);
ints.forEach(System.out::println);
}
}
추가로 IntStream과 LongStream에 정의된 range()나 rangeClosed() 메서드를 사용하면 다음과 같이 특정 범위의 정수값을 스트림으로 생성해서 반환하는 것도 가능하다.
import java.util.stream.IntStream;
public class StreamCreator {
public static void main(String[] args) {
//특정 범위의 정수
IntStream intStream = IntStream.rangeClosed(1, 10);
intStream.forEach(System.out::println);
}
}
//출력값
12345678910
rangeClosed()와 range()의 차이는 두 번째로 전달되는 매개변수가 범위 안에 포함되는지 여부에 따라 구분될 수 있다. rangeClosed()는 끝 번호가 포함되어 위의 코드 예제처럼 1~10까지의 숫자가 출력되는 반면 range()의 경우에는 끝번호가 포함되지 않아 1~9 까지의 숫자가 출력된다.
스트림의 중간 연산
스트림의 중간 연산자의 결과는 스트림을 반환하기 때문에 여러개의 연산자를 연결하여 원하는 데이터 처리를 수행할 수 있다.
그중 가장 빈번하게 사용되는 중간 연산자는 필터링, 매핑, 정렬 등이 있다. 전체적인 코드 구조는 아래와 같다.
필터링(filter() , distinct() )
필터링은 필요에 따라 조건에 맞는 데이터들만 정제하는 역할을 하는 중간 연산자이다.
- distinct(): 스트림의 요소들에 중복된 데이터가 존재하는 경우 중복을 제거하기 위해 사용한다.
- filter(): 스트림에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어낸다. filter()메서드에는 매개값으로 조건(Predicate)을 주고, 조건이 참이 되는 요소만 필터링 한다. 여기서 조건은 람다식을 사용하여 정의할 수 있다.
import java.util.Arrays;
import java.util.List;
public class FilteringExample {
public static void main(String[] args) throws Exception {
List<String> names = Arrays.asList("김코딩", "이자바", "박해커", "김코딩", "박해커");
names.stream()
.distinct() //중복 제거
.forEach(element -> System.out.println(element));
System.out.println();
names.stream()
.filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링
.forEach(element -> System.out.println(element));
System.out.println();
names.stream()
.distinct() //중복제거
.filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링
.forEach(element -> System.out.println(element));
}
}
// 출력값
김코딩
이자바
박해커
김코딩
김코딩
김코딩
위의 코드 예제는 필터링 과정을 순차적으로 잘 보여주고 있다. 가장 첫 번째 스트림에서는 중복 제거 중간 연산만 수행하여 리스트 타입의 참조변수 names 안에 중복되고 있는 김코딩 요소를 제거 했다. 두 번째 스트림에서는 김 씨 성을 가진 요소들만 필터링하여 김코딩을 두 번 출력해주고 있다. 마지막 세번째 스트림은 중복제거와 필터링을 모두 수행하였고 그 결과로 김코딩 이라는 문자열을 단 한 번 출력하고 있다.
매핑(map())
매핑은 스트림 내 요소들에서 원하는 필드만 추출하거나 특정 형태로 변환할 때 사용하는 중간연산자이다. 위의 filter()메서드와 마찬가지로 값을 변환하기위한 조건을 람다식으로 정의한다.
import java.util.Arrays;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("kimcoding", "javalee", "hackerna", "luckyguy");
names.stream()
.map(element -> element.toUpperCase()) // 요소들을 하나씩 대문자로 변환
.forEach(element->System.out.println(element));
}
}
// 출력값
KIMCODING
JAVALEE
HACKERNA
LUCKYGUY
위 예제는 리스트 타입의 참조변수 names 안에 정의된 각 요소를 순회하면서 소문자 이름을 대문자로 변환한 값들이 담긴 스트림으로 반환하는 연산과정을 보여주고 있다.
또한 다음과 같이 각 요소에 어떤 연산을 실행하고 난 후의 값을 반환받을 수 있다. 아래의 코드 예제는 각 요소의 값에 3을 곱한 값을 스트림으로 반환하여 출력하는 과정을 보여주고 있다.
import java.util.Arrays;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args)
{
List<Integer> list = Arrays.asList(1, 3, 6, 9);
// 각 요소에 3을 곱한 값을 반환
list.stream().map(number -> number * 3).forEach(System.out::println);
}
}
// 출력값
3
9
18
27
map()과 함께 많이 사용되는 flatMap() 중간 연산자도 있다.
아래의 이중 배열에서 map()메서드를 사용하여 하나식 출력해주는 프로그램을 만든다고 가정해보자.
// 주어진 이중 배열
String[][] namesArray = new String[][]{{"박해커", "이자바"}, {"김코딩", "나박사"}};
// 기대하는 출력값
박해커
이자바
김코딩
나박사
// map() 사용
Arrays.stream(namesArray)
.map(inner -> Arrays.stream(inner))
.forEach(System.out::println);
// 출력값
java.util.stream.ReferencePipeline$Head@3cb5cdba
java.util.stream.ReferencePipeline$Head@56cbfb61
출력값은 기대와 다르게 스트림 객체의 값을 반환할 것이다. 왜냐하면 위의 연산에서 map() 메서드는 Stream<Stream<String>> 즉, 중첩 스트림을 반환하고 있기 때문이다. 여기서 우리가 원하는 결과값을 출력하기 위해서는 반환타입이 Stream<String>이 되어야 한다.
원하는 결과값을 얻기 위해서는 아래와 같이 코드를 수정해야 한다.
// map 사용
Arrays.stream(namesArray)
.map(inner -> Arrays.stream(inner))
.forEach(names -> names.forEach(System.out::println));
// 출력값
박해커
이자바
김코딩
나박사
forEach() 메서드 안의 람다식의 정의에서 각 요소에 대하여 다시 forEach() 메서드를 출력함으로써 뎁스가 있는 요소들에 접근하여 출력할 수 있다.
하지만 지금처럼 이중구조가 아닌 뎁스가 3중, 4중, 5중 으로 깊어지면 가독성이 떨어질 것이다.이런경우 우리는 flatMap()을 활용할 수 있다.
// flatMap()
Arrays.stream(namesArray).flatMap(Arrays::stream).forEach(System.out::println);
// 출력값
박해커
이자바
김코딩
나박사
flatMap()은 중첩 구조를 제거하고 단일 컬렉션 (Stream<String>)으로 만들어주는 역할을 한다. 이를 요소들을 플래트닝(flattening) 한다고 한다.
정렬(sorted())
sorted() 메서드는 정렬할 때 사용하는 중간연산자이다.
sorted() 메서드를 사용하여 정렬할 때 괄호안에 Comparator라는 인터페이스에 정의된 static 메서드와 디폴트 메서드를 사용하여 간편하게 정렬 작업을 수행할 수 있다.
- 기본 정렬
import java.util.Arrays;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 동물들의 이름을 모아둔 리스트
List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
// 인자값 없는 sort() 호출
animals.stream().sorted().forEach(System.out::println);
}
}
// 출력값
Cow
Duck
Horse
Lion
Monkey
Tiger
- 역순으로 정렬
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args) {
List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
// 인자값에 Comparator 인터페이스에 규정된 메서드 사용
animals.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
}
}
// 출력값
Tiger
Monkey
Lion
Horse
Duck
Cow
Comparator 인터페이스 안에 정의된 reverseOrder() 라는 이름의 메서드를 인자로 넘겨 호출하는 것만으로 손쉽게 역순으로 정렬하는 기능을 실행할 수 있다.
기타 중간 연산자
- skip() - 스트림의 일부 요소들을 건너뛴다.
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 1~10 범위의 정수로 구성된 스트림 생성
IntStream intStream = IntStream.rangeClosed(1, 10);
// 앞의 5개의 숫자를 건너뛰고 숫자 6부터 출력
intStream.skip(5).forEach(System.out::println);
}
}
// 출력값
6
7
8
9
10
- limit() - 스트림의 일부를 자른다.
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 1~10 범위의 정수로 구성된 스트림 생성
IntStream intStream = IntStream.rangeClosed(1, 10);
// 앞에서부터 5개의 숫자만 출력
intStream.limit(5).forEach(System.out::println);
}
}
// 출력값
1
2
3
4
5
- peak() - 요소들을 순회하며 특정 작업을 수행한다.
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 요소들을 사용하여 IntStream 생성
IntStream intStream3 = IntStream.of(1, 2, 2, 3, 3, 4, 5, 5, 7, 7, 7, 8);
// 짝수만 필터링하여 합계 구하기
int sum = intStream3.filter(element -> element % 2 == 0)
.peek(System.out::println)
.sum();
System.out.println("합계 = " + sum);
}
}
// 출력값
2
2
4
8
합계 = 16
스트림의 최종 연산자 - 기본 집계 (sum(), count(), average(), max(), min())
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) {
// int형 배열 생성
int[] intArray = {1,2,3,4,5};
// 카운팅
long count = Arrays.stream(intArray).count();
System.out.println("intArr의 전체 요소 개수 " + count);
// 합계
long sum = Arrays.stream(intArray).sum();
System.out.println("intArr의 전체 요소 합 " + sum);
// 평균
double average = Arrays.stream(intArray).average().getAsDouble();
System.out.println("전체 요소의 평균값 " + average);
// 최대값
int max = Arrays.stream(intArray).max().getAsInt();
System.out.println("최대값 " + max);
// 최소값
int min = Arrays.stream(intArray).min().getAsInt();
System.out.println("최소값 " + min);
// 배열의 첫 번째 요소
int first = Arrays.stream(intArray).findFirst().getAsInt();
System.out.println("배열의 첫 번째 요소 " + first);
}
}
// 출력값
intArr의 전체 요소 개수 5
intArr의 전체 요소 합 15
전체 요소의 평균값 3.0
최대값 5
최소값 1
배열의 첫 번째 요소 1
매칭(allMatch(), anyMatch(), noneMatch())
- allMatch() - 모든 요소가 조건을 만족하는지 여부를 판단한다.
- noneMatch() - 모든 요소가 조건을 만족하지 않는지 여부를 판단한다.
- anyMatch() - 하나라도 조건을 만족하는 요소가 있는지 여부를 판단한다.
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) throws Exception {
// int형 배열 생성
int[] intArray = {2,4,6};
// allMatch()
boolean result = Arrays.stream(intArray).allMatch(element-> element % 2 == 0);
System.out.println("요소 모두 2의 배수인가요? " + result);
// anyMatch()
result = Arrays.stream(intArray).anyMatch(element-> element % 3 == 0);
System.out.println("요소 중 하나라도 3의 배수가 있나요? " + result);
// noneMatch()
result = Arrays.stream(intArray).noneMatch(element -> element % 3 == 0);
System.out.println("요소 중 3의 배수가 하나도 없나요? " + result);
}
}
// 출력값
요소 모두 2의 배수인가요? true
요소 중 하나라도 3의 배수가 있나요? true
요소 중 3의 배수가 하나도 없나요? false
요소소모(reduce())
스트림의 요소를 줄여나가면서 연산을 수행하고 최종적인 결과를 반환한다.
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) throws Exception {
int[] intArray = {1,2,3,4,5};
// sum()
long sum = Arrays.stream(intArray).sum();
System.out.println("intArray 전체 요소 합: " + sum);
// 초기값이 없는 reduce()
int sum1 = Arrays.stream(intArray)
.map(element -> element * 2)
.reduce((a , b) -> a + b)
.getAsInt();
System.out.println("초기값이 없는 reduce(): " + sum1);
// 초기값이 있는 reduce()
int sum2= Arrays.stream(intArray)
.map(element -> element * 2)
.reduce(5, (a ,b) -> a + b);
System.out.println("초기값이 있는 reduce(): " + sum2);
}
}
// 출력값
intArray 전체 요소 합: 15
초기값이 없는 reduce(): 30
초기값이 있는 reduce(): 35
요소 수집(collect())
스트림은 중간 연산을 통한 요소들의 데이터 가공 후 요소들을 수집하는 최종 처리 메서드인 collect()를 지원한다. 좀 더 구체적으로, 스트림의 요소들을 List, Set, Map 등 다른 타입의 결과로 수집하고 싶은 경우에 collect() 메서드를 유용하게 사용할 수 있다. collect() 메서드는 Collector 인터페이스 타입의 인자를 받아서 처리할 수 있는데, 직접 구현하거나 미리 제공된 것들을 사용할 수 있으며 빈번하게 사용되는 기능들은 Collectors 클레스에서 제공하고 있다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TerminalOperationExample {
public static void main(String[] args) {
// Student 객체로 구성된 배열 리스트 생성
List<Student> totalList = Arrays.asList(
new Student("김코딩", 100, Student.Gender.Male),
new Student("박해커", 80, Student.Gender.Male),
new Student("이자바", 90, Student.Gender.Female),
new Student("나미녀", 60, Student.Gender.Female)
);
// 스트림 연산 결과를 Map으로 반환
Map<String, Integer> maleMap = totalList.stream()
.filter(s -> s.getGender() == Student.Gender.Male)
.collect(Collectors.toMap(
student -> student.getName(), // Key
student -> student.getScore() // Value
));
// 출력
System.out.println(maleMap);
}
}
class Student {
public enum Gender {Male, Female};
private String name;
private int score;
private Gender gender;
public Student(String name, int score, Gender gender) {
this.name = name;
this.score = score;
this.gender = gender;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Gender getGender() {
return gender;
}
}
// 출력값
{김코딩=100, 박해커=80}
'공부 > 전자컴퓨터공학' 카테고리의 다른 글
Java 스레드(Thread)란? 자바 기초 배우기 (1) | 2024.05.31 |
---|---|
Java Optional Class 란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 람다식(Lambda Expression)이란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 애너테이션(Annotation) 이란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 컬렉션 프레임워크란? 자바 기초 배우기 (0) | 2024.05.30 |