Stream(스트림)이란?
- -
Java 는 많은 양의 데이터를 처리 하기 위해서 배열, 컬렉션 등을 이용합니다.
저장된 배열, 컬렉션들은 반복문(for), 반복자(Iterator) 를 사용하여 데이터 형식에 맞게
새로운 코드를 작성했다.
하지만 이러한 반복문, 반복자를 이용한 코드는 가독성과 재사용성 그리고 반복되는 코드들이
너무 많았다.
또한 Collection 이나 Iterator 와 같은 인터페이스의 각 컬렉션 클래스들은 같은 기능이지만
중복해서 정의되어 있다.
아래 코드를 보면 Collection 과 Arrays 의 sort 함수이다.
List<String> arr = new ArrayList<String>();
arr.add("banana");
arr.add("apple");
arr.add("melon");
String[] arr2 = { "apple", "melon", "banana"};
Collections.sort(arr);
Arrays.sort(arr2);
System.out.println("Collections Sort : "+arr);
for(String s : arr2) {
System.out.println("Arrays Sort : "+s);
}
// 결과값
Collections Sort : [apple, banana, melon]
Arrays Sort : apple
Arrays Sort : banana
Arrays Sort : melon
굉장히 코드가 길고 가독성이 떨어진다.
이러한 문제를 해결하기 위해서 Java8 에서 Stream 이라는게 나왔다.
Stream 이란?
사전적으로는 데이터의 연속적인 흐름이라는 뜻을 가지고 있으며,
데이터 소스를 추상화하여 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되어
코드 재사용성이 굉장히 높아졌다.
아래 코드는 위 코드와 다르게 Stream 을 사용하여 sort() 함수를 사용했다.
List<String> arr = new ArrayList<String>();
arr.add("banana");
arr.add("apple");
arr.add("melon");
String[] arr2 = { "apple", "melon", "banana"};
Stream<String> stream_str = arr.stream();
Stream<String> arrays_stream = Arrays.stream(arr2);
stream_str.sorted().forEach(System.out::println);
System.out.println("----------------------------");
arrays_stream.sorted().forEach(System.out::println);
// 결과값
apple
banana
melon
----------------------------
apple
banana
melon
배열과 Collection 을 이용한 코드보다도 훨씬 더 간결해지고 가독성이 좋아졌다.
Stream 장점
Stream 의 가장 큰 장점은 복잡한 코드를 사용하지 않고 데이터 소스를 병렬 처리 할 수 있다.
제공하는 연산은 크게 두가지로 "중간 연산" 과 "최종 연산" 으로 분류할 수 있다.
// Car 객체
@Getter
@Setter
public class Car {
public Car(String name, int price) {
this.name = name;
this.price = price;
}
String name; // 자동차 이름
Integer price; // 가격
}
// Stream 병렬 처리
List<Car> cars = Arrays.asList(
new Car("아반떼", 20000000),
new Car("아반떼 업글", 25000000),
new Car("그랜져", 30000000),
new Car("쏘렌토", 40000000)
);
List<String> priceSort = cars.stream()
.filter(car -> car.getPrice() <= 30000000) // 중간연산
.sorted((car1, car2) -> car1.getPrice().compareTo(car2.getPrice())) // 중간연산
.map(Car::getName) // 중간연산
.collect(Collectors.toList()); // 최종연산
System.out.println("priceSort : "+priceSort);
위처럼 stream 을 통해 선언형으로 로직을 처리할 수 있으며, 람다 표현식을 사용해
훨씬 더 가독성이 좋은 코드를 구현할 수 있다.
중간연산 메서드
중간 연산 | 반환 타입 | 설 명 |
distinct() | Stream<T> | 중복을 제거 |
filter(Predicate<T> predicate) | 조건에 안 맞는 요소 제외 | |
limit(long maxSize) | 스트림의 일부를 잘라낸다. | |
skip(long n) | 스트림의 일부를 건너뛴다. | |
peek(Cosumer<T> action) | 스트림의 요소에 작업수행 | |
sorted() | 스트림의 요소를 정렬 | |
sorted(Comparator<T> comparator) | 구분자를 이용하여 스트림의 요소를 정렬 | |
map(Fuction<T, R> mapper) | Stream<R> | 스트림의 요소를 변환 |
flatMap(Fuction<T, Stream<R>> mapper) | ||
mapToInt(ToIntFunction mapper) | IntStream | |
flatMapToInt(Function<t, intstream=""> m)</t,> | ||
mapToDouble(ToDoubleFunction<T> mapper) | DoubleStream | |
flatMapToDouble(Function<T, DoubleStream> m) | ||
MapToLong(ToLongFunction mapper) | LongStream | |
flatMapToLong(Function<t, LongStream> m) |
최종연산 메서드
최종 연산 | 반환 타입 | 설 명 |
forEach(Consumer<? super T> action) | void | 각 요소에 지정된 작업 수행 |
forEachOrdered(Consumer action) | ||
count() | long | 스트림의 요소의 개수 반환 |
max(Comparator<? super T> comparator) | Optional<T> | 스트림의 최대값을 반환 |
min(Comparator<? super T> comparator) | 스트림의 최소값을 반환 | |
findAny() | 스트림의 요소 중 아무거나 하나를 반환 | |
findFirst() | 스트림의 첫 번째 요소를 반환 | |
allMatch(Predicate<T> p) | boolean | 주어진 조건을 모두 만족시키는지 확인 |
anyMatch(Predicate<T> p) | 주어진 조건을 하나라도 만족시키는지 확인 | |
noneMatch(Predicate<T> p) | 주어진 조건을 모두 만족하지 않는지 확인 | |
toArray() | Object[] | 스트림의 모든 요소를 배열로 반환 |
toArray(IntFunction<A[]> generator) | A[] | |
reduce(BinaryOperator<T> accumulator) | Optional<T> | 스트림의 요소를 하나씩 줄여가며 (리듀싱) 계산한다. |
reduce(T identity, BinaryOperator<T> accumulator) | T | |
reduce(U identity, BinaryOperator<U,T<U> accumulator, BinaryOperator<U> combiner) | U | |
collect(Collector<T,A,R> collector) | R | 스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다. |
collect(Supplier<R> supplier, BiConsumer<R,T> accumlator, BiConsumer<R,R> combiner) |
Stream(스트림) 의 특징
1. 스트림은 데이터를 변경하지 않는다.
스트림은 데이터를 읽기만할뿐 데이터 소스를 변경하지 않는다.
결과값은 컬렉션이나 배열에 담아 반환할 수 있다.
List<String> arr = new ArrayList<String>();
arr.add("banana");
arr.add("apple");
arr.add("melon");
Stream<String> stream_str = arr.stream();
List<String> sort_list = stream_str.sorted().collect(Collectors.toList());
System.out.println("sort_list : "+sort_list);
// 결과값
sort_list : [apple, banana, melon]
위처럼 stream의 sorted() 메서드와 collect() 메서드를 통해 컬렉션에 담아 반환했다.
반환은 가능하지만 스트림은 데이터 소스를 변경하지 않는다.
2. 스트림은 일회성이다.
스트림은 Iterator 처럼 일회성이다.
Iterator 는 컬렉션의 요소를 모두 읽을때까지 반복하는데 더이상 읽을 요소가 없을 경우 종료된다.
스트림도 마찬가지로 한번 사용하고 최종연산을 끝낸 후에는 다시 사용할 수 없다.
사용하기 위해선 재생성하여 사용해야한다.
3. 스트림은 작업을 내부 반복으로 처리한다.
스트림이 간결한 코드로 작성될 수 있는 이유중에 하나가 "내부 반복"이다.
컬렉션이나 배열같은 경우 사용자가 for, forEach, while 등 반복문을 통해 요소들을 반복하는데
이를 "외부 반복" 이라고 한다.
하지만 스트림은 내부에서 반복을 알아서 처리해주고 결과 데이터만 반환 및 저장해주는 내부 반복을 사용한다.
'Java > Spring' 카테고리의 다른 글
Spring Security Filter Chain (0) | 2024.06.17 |
---|---|
Array(배열) 과 ArrayList 차이 (0) | 2024.05.29 |
logback 설정 (0) | 2024.05.20 |
SecurityContext, SecurityContextHolder (0) | 2024.02.23 |
Spring Security 5 버전에서 Spring Security 6 버전으로 올라가면서 변경된 SecurityContext 에 인증 객체 설정 (2) | 2024.02.22 |
소중한 공감 감사합니다