반응형
오류가 있으면 언제든지 지적해 주세요. 생각을 공유하는 것도 좋아합니다!
목표
- Stream의 특징을 이해한다.
- Stream의 문법을 훑어본다.
- 기존의 반복문, 컬렉션과 비교하고 차이점을 이해한다.
1. 특징
- Java8에서 추가됐다.
- 원본 데이터를 변경하지 않는다.
- 내부 반복자를 사용한다.
- 람다식과 함께 사용할 수 있다.
- 지연 연산을 수행한다.
- 병렬 처리를 할 수 있다.
- 일회용이다. 닫힌 스트림을 사용하면 예외가 발생한다.
2. 문법
실행 순서
Stream pipeline을 따라서
- 데이터 소스(array, collection,...)
- 중간 연산(map, filter,...)
- 마지막 연산(count, sum,...)
위의 순서대로 실행된다.
Stream의 종류
Stream 종류 | 용도 |
---|---|
IntStream | int 원시 타입을 지원함 |
LongStream | long 원시 타입을 지원함 |
DoubleStream | double 원시 타입을 지원함 |
Stream | 모든 객체 타입을 지원함 |
스트림 생성 메소드
메소드 | 용도 |
---|---|
Collection.stream() | Collection을 상속받은 객체의 원소로 Stream 생성 |
Arrays.stream(array) | array의 원소로 Stream 생성 |
Arrays.stream(array, start, end) | array의 [start, end) 범위의 원소로 Stream 생성 |
중간 연산 메소드
반환 값이 Stream이고 스트림을 닫지 않기 때문에 메소드 체이닝이 가능하다.
메소드 정의 | 용도 |
---|---|
Stream<T> distinct() | 중복 제거 |
Stream<T> sorted() | 정렬 |
Stream<T> filter(Predicate<T> predicate) | 조건에 맞는 원소만 선택 |
Stream<T> limit(long maxSize) | maxSize까지 원소만 선택 |
Stream<T> skip(long n) | [0, n)을 제외한 원소 선택 |
Stream<T> peek(Consumer<T> action) | action 함수 실행, 원소 그대로 리턴 |
Stream<R> map(Function<? super T, ? extends R> mapper) | mapper 함수 적용한 원소로 리턴 |
Stream<R> flatMap(Function<T, stream<? extends R>> Tmapper) | map의 결과를 하나의 스트림으로 리턴 |
마지막 연산 메소드
마지막 연산이기 때문에 이 메소드가 실행되면 스트림을 닫는다.
메소드 정의 | 용도 |
---|---|
void forEach(Consumer <? super T> action) | action 함수 실행 |
long count() | 원소 수 반환 |
Optional<T> sum(Comparator <? super T> comparator) | 원소 합 |
Optional<T> max(Comparator <? super T> comparator) | 원소 최대 값 |
Optional<T> min(Comparator <? super T> comparator) | 원소 최소 값 |
Optional<T> findAny() | 랜덤 원소 |
Optional<T> findFirst() | 처음 원소 |
boolean allMatch(Pradicate<T> p) | 모든 원소가 true라면 true |
boolean anyMatch(Pradicate<T> p) | 하나라도 true라면 true |
boolean noneMatch(Pradicate<T> p) | 다 false라면 true |
Object[] toArray() | 배열로 반환 |
Stream source 코드 예제
1. 배열
Arrays.stream 메소드를 사용해서 Stream을 생성할 수 있다.
// Array -> Stream
String[] names = new String[]{"mike", "jun", "do", "yu"};
Stream<String> nameStream = Arrays.stream(names);
Stream<String> nameRangeStream = Arrays.stream(names, 0, 2); // 범위 [0, 2)
2. Collection
stream 메소드를 사용해서 Stream을 생성할 수 있다.
// List -> Stream
List<String> strings = Arrays.asList("one", "two", "three");
Stream<String> stringStream = strings.stream();
3. Map
entrySet 메소드와 Map.Entry 인터페이스를 사용해서 Stream을 생성할 수 있다.
// Map -> Stream
Map<String, Integer> map = new HashMap<>() {{
put("one", 1);
put("two", 2);
put("three", 3);
}};
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Stream<Map.Entry<String, Integer>> entryStream = entries.stream();
4. Builder
Stream.builder 메소드를 사용해서 Stream을 생성할 수 있다.
// Builder
IntStream intStream = IntStream.builder().add(1).add(2).add(3).add(4).build();
Stream 중간 작업 코드 예제
루프 퓨전과 쇼트서킷을 생각하고, 중간 작업의 순서에 주의해서 사용해야 한다.
// 출력 결과가 예상과 일치하는지 확인하세요.
String[] names = new String[]{"do", "yu", "do", "yu", "doyu"};
Stream<String> nameStream = Arrays.stream(names);
nameStream.peek(System.out::println).distinct()
.peek((name)-> System.out.println("after distinct:" + name)).toList();
/*
do
after distinct:do
yu
after distinct:yu
do
yu
doyu
after distinct:doyu
*/
Stream 마지막 작업 코드 예제
Map<String, Integer> map = new HashMap<>() {{
put("one", 1);
put("two", 2);
put("three", 3);
}};
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Stream<Map.Entry<String, Integer>> entryStream = entries.stream();
Optional<Integer> max = entryStream.map(Map.Entry::getValue).max((a, b) -> {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
});
max.ifPresent(System.out::println);
3. 비교
기존의 반복문과 컬렉션이 있는데, Stream은 어떤 문제를 해결하기 위해서 언제 사용할까?
For-loop VS Stream
상대적인 관점에서 특징을 정리했다.
For-loop | Stream |
---|---|
처리 속도가 빠르다 | 처리 속도가 느리다 |
가독성이 나쁘다 | 가독성이 좋다 |
병렬 처리가 어렵다 | 병렬 처리가 쉽다 |
외부 반복자를 사용한다 | 내부 반복자를 사용한다 |
일반적으로 즉시 연산을 수행한다 | 지연 연산을 수행한다 |
모든 인터페이스를 사용할 수 있다 | 함수적 인터페이스를 사용한다 |
Collection VS Stream
Collection | Stream |
---|---|
원소의 효율적인 관리 및 접근이 주 목적 | 원소들에 전체적으로 수행될 계산 작업을 선언적으로 설명하는 것이 주 목적 |
저장 공간이 있다 | 저장 공간이 없다 |
결론
- Stream은 원소들에 전체적으로 수행될 작업을 함수적 인터페이스를 사용해서 가독성 좋게 처리하고 싶을 때 사용한다.
- Stream은 병렬 처리가 쉽다. 그러므로 병렬 처리가 유리한 상황에서 Stream을 사용한다.
추가
1. Stream은 언제 느릴까?
- 루프 퓨전과 쇼트서킷이 완벽하게 최적화하지 못하는 경우에 느리다.
- Ex) sort같이 모든 원소를 확인해야 하는 경우 쇼트서킷이 불가능하다.
- 순차 처리가 더 유리한 상황에서 병렬 처리를 사용했을 때 느리다.
2. 병렬 처리가 유리한 상황은 언제일까?
- 멀티 코어 CPU를 사용하고(병렬성), 처리할 원소가 많아서 쓰레드 사용에 드는 오버헤드보다 쓰레드 사용의 이점이 더 클 때 유리하다.
3. 내부 반복자의 장점이 뭘까?
- 원소를 처리하는 함수만 제공하면 된다.
- 개발자는 루프 퓨전과 쇼트서킷을 신경 쓰지 않아도 된다.
4. Stream은 왜 지연 연산을 수행할까?
- 반대로 즉시 연산을 해야 할 필요성이 있을까?
- 지연 로딩을 사용하면 루프 퓨전과 쇼트서킷으로 최적화가 가능하다.
추후에 위와 관련된 자세한 글을 써야겠다.
참고 문서: 오라클 자바 8 Stream
반응형
'Tool > Java' 카테고리의 다른 글
[JPA] MultipleBagFetchException 이해하기 (1) | 2024.01.04 |
---|---|
[Spring] JPA 복합 키 사용하기 (0) | 2023.12.20 |
[Java] 리플렉션 API 간단 정리 (0) | 2023.10.23 |
[Java] CUI 키오스크 (내일배움캠프 과제) (0) | 2023.10.20 |