Java
[Java] Stream
snail voyager
2023. 7. 31. 23:39
728x90
반응형
Stream
- 데이터의 흐름
- Collection 형태로 구성된 데이터를 람다를 이용해 간결하고 직관적으로 프로세싱
- for, while 등 기존 loop 를 대체
- 병렬처리 가능
- 데이터 소스를 변경하지 않음
- 재사용 불가
- 컬렉션은 현재 자료구조가 포함되는 모든 값을 메모리에 저장하는 자료구조
- 스트림은 요청할 때만 요소를 계산하는 고정된 자료구조
- 내부반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택
- 지연된 연산 : 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한번에 처리
Filter
- 만족하는 데이터만 걸러내는데 사용
- Predicate에 true를 반환하는 데이터만 존재하는 stream을 리턴
- 중간처리 기능 Intermediate Operation, 여러가지 중간처리 이어붙이기 가능
Stream<T> filter (Predicate<? super T> predicate)
TakeWhile
- Java 9 부터 추가
- 스트림의 요소를 처음부터 순서대로 검사하면서, 지정된 조건을 만족하는 요소들만 가져오고,
- 해당 조건을 만족하지 않는 요소를 만나면 스트림의 처리를 중단
- 스트림의 요소가 정렬되어 있을 때에만 의미
- filter 는 스트림의 모든 요소를 검사하기 때문에, 조건에 맞지 않는 모든 요소를 걸러낸 후에도 전체 스트림을 반환
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 스트림에서 조건을 만족하는 요소들만 가져옴
List<Integer> filteredNumbers = numbers.stream()
.takeWhile(num -> num < 6)
.collect(Collectors.toList());
System.out.println(filteredNumbers); // [1, 2, 3, 4, 5]
DropWhile
- 스트림의 요소를 처음부터 순서대로 검사하면서, 지정된 조건을 만족하는 동안은 해당 요소들을 건너뛰고,
- 조건을 만족하지 않는 첫 번째 요소부터 나머지 모든 요소들을 반환
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 스트림에서 조건을 만족하는 동안의 요소들을 건너뛰고, 그 이후의 모든 요소들을 반환
List<Integer> remainingNumbers = numbers.stream()
.dropWhile(num -> num < 6)
.collect(Collectors.toList());
System.out.println(remainingNumbers); // [6, 7, 8, 9, 10]
Map
- 데이터를 변형하는데 사용
- 데이터에 해당 함수가 적용된 결과물을 제공하는 stream을 리턴
- 중간처리 기능 Intermediate Operation, 여러가지 중간처리 이어붙이기 가능
<R> Stream<R> map (Function<? super T, ? extends R> mapper)
FlatMap
- 스트림의 각 요소에 대해 변환 함수를 적용하고,
그 결과로 생성된 여러 개의 스트림을 한 개의 평면화된 스트림으로 결합 - 리스트의 리스트인 경우 이를 평면화하여 단일 리스트로 만들어
중첩된 구조를 펼치고 단일 리스트로 처리하는 것이 가능
List<String> sentences = Arrays.asList("Hello world", "Java programming", "Stream API");
// 각 문자열을 단어로 분리하여 평면화된 스트림 생성
List<String> words = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println(words); // [Hello, world, Java, programming, Stream, API]
peek
- 디버깅 및 로깅, 요소를 검사하고 로그에 출력하거나 특정 조건을 확인하기 위해 사용
- 요소를 소비하지 않고 새로운 스트림을 생성하지 않고 각 요소를 소비한 것처럼 동작을 실행
- peek()은 자신이 확인한 요소를 파이프라인의 다음 연산으로 그대로 전달
List<Integer> result = numbers.stream()
.peek(x -> System.out.println("from stream: " + x))
.map(x -> x + 17)
.peek(x -> System.out.println("after map: " + x))
.filter(x -> x % 2 == 0)
.peek(x -> System.out.println("after filter: " + x))
.limit(3)
.peek(x -> System.out.println("after limit: " + x))
.collect(toList());
anyMatch
- 적어도 한 요소와 일치하는지 확인
- boolean을 반환하는 최종 연산
- 스트림이 비어 있으면 항상 false를 반환
allMatch
- 모든 요소와 일치하는지 확인
- 스트림이 비어 있으면 항상 true를 반환
noneMatch
- allMatch와 반대 연산
- anyMatch, allMatch, noneMatch 쇼트서킷 기법
- 스트림이 비어 있으면 항상 true를 반환
findAny
- 스트림에서 임의의 요소를 반환
- Optional 반환
- 요소의 반환 순서가 상관없다면 병렬 스트림에서는 제약이 적은 findAny 사용
findFirst
- 첫 번째 요소 찾기
reduce
- 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리하는 연산
- 첫번째 인수는 초깃값
- 두번째 인수는 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
- 초깃값이 없으면 Optional 반환
- 결과를 누적할 내부 상태가 필요하여 상태 있는 연산
- 세번째 인자는 병렬 처리 시 병렬 스트림의 부분 결과를 병합할 때 사용되는 BinaryOperator<T>
int sum = numbers.stream().reduce(0, (a,b) -> a+b);
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Sorted
- 정렬된 stream을 리턴
- Comparator 활용
Stream<T> sorted(Comparator<? super T> comparator)
Distinct
- 중복된 데이터가 제거된 stream을 리턴
- 객체의 equals() 구현 필요
- 정렬하거나 중복을 제거하려면 과거의 이력을 알고 있어야해서 모든 요소가 버퍼에 추가됨
- 내부 상태를 갖는 연산
Stream<T> distinct()
기본형 특화 스트림
- IntStream, DoubleStream, LongStream
- 오직 박싱 과정에서 일어나는 효율성과 관련 있으며 스트림에 추가 기능을 제공하지 않는다
- 스트림을 숫자 특화 스트림으로 변환할 때 mapToInt, mapToDouble, mapToLong
Stream<T> 대신 특화된 스트림 반환 - boxed 메서드를 이용해서 특화 스트림을 일반 스트림으로 반환
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
- OptionalInt, OptionalDouble, OptionalLong 값이 없는 상황에 기본값을 명시적으로 정의
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max(); //값이 없을 수도 있음
int max = maxCalories.orElse(1);
숫자 범위
- IntStream, LongStream에서 range, rangeClosed 정적 메소드 제공
- range 메서드는 시작값 포함, 종료값 미포함
- rangeClosed 메서드는 시작값, 종료값 포함
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);
값으로 스트림 만들기
Stream<String> stream = Stream.of("A", "B", "C");
Stream<String> emptyStream = Stream.empty();
null 이 될 수 있는 객체로 스트림 만들기
Stream<String> homeValueStream = Stream.ofNullable(System.getProperty("home"));
Stream<String> values = Stream.of("config", "home", "user")
.flatMap(key -> Stream.ofNullable(System.getProperty(key));
배열로 스트림 만들기
int[] numbers = {1,2,3,4,5};
int sum = Arrays.stream(numbers).sum();
무한 스트림
- 일반적으로 연속된 일련의 갑슬 만들 때는 interate
- generate는 생산된 각 값을 연속적으로 계산하지 않는다
- 무한한 값을 출력하지 않도록 limit 함께 사용
IntStream.iterate(0, n-> n<100, n->n+4) //두번째 인수로 언제까지 작업을 수행할 것인지 기준
.forEach(System.out::println);
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
Parallel Stream
- 여러개의 스레드를 이용하여 stream의 처리 과정을 병렬화
- 중간 과정은 병렬 처리되지만 순서가 있는 stream의 경우 종결 처리했을 때의 결과물이
기존의 순차적 처리와 일치하도록 종결 처리과정에서 조정 (중간 처리 순서 보장 X) - 장점
- 굉장히 간단하게 병렬 처리를 사용
- 속도가 비약적으로 빨라짐
- 단점
- 항상 속도가 빨라지는 것은 아님
- 공통으로 사용하는 리소스가 있을 경우 잘못된 결과가 나오거나 오류가 날 수도 (deadlock)
- mutex, semaphore 등 병렬 처리 기술을 이용하면 sequential보다 느려질 수도
Stream<Integer> parallelStream = numbers.parallelStream();
Stream<Integer> parallelStream2 = numbers.stream().parallel();
Scope
- 변수에 접근할 수 있는 범위
- 함수 안에 함수가 있을 때 내부 함수에서 외부 함수에 있는 변수에 접근 가능 (lexical scope)
Closure
- 내부 함수가 존재하는 한 내부 함수가 사용한 외부 함수의 변수들 역시 계속 존재
- lexical scope를 포함하는 함수를 closure
- 내부 함수가 사용한 외부 함수의 변수들은 내부 함수 선언 당시로부터 변할 수 없기 때문에
final로 선언되지 않더라도 final 취급
Curry
- 여러 개의 매개변수를 받는 함수를 중첩된 여러 개의 함수로 쪼개어 매개 변수를 한 번에 받지 않고
여러 단계에 걸쳐 나눠 받을 수 있게 하는 기술
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
=>
Function<Integer, Function<Integer, Integer>> add =
x -> y -> x + y;
Lazy Evaluation
- Lambda의 계산은 그 결과값이 필요할 때가 되어서야 계산
- 불필요한 계산을 줄이거나 해당 코드의 실행 순서를 의도적으로 미룰 수 있다.
Function Composition
- 여러 개의 함수를 합쳐 하나의 새로운 함수로 만드는 것
<V> Function<V, R> compose(Function<? super V, ? extends T> before) //파라미터 먼저 실행
<V> Function<T, V> andThen(Function<? super R, ? extends V> after) //자신 먼저 실행
728x90
반응형