다양한 데이터 소스(Collection, 배열 등과 같이 여러 데이터를 저장하고 있는 것)를 표준화된 방법으로 다루기 위한 것
- List, Set, Map 의 성격이 다르기 때문에 표준화하기 어려운데 stream이 등장하면서 표준화될 수 있음
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream(); // 컬렉션
Stream<Integer> strStream = Stream.of(new String[]{"a", "b", "c"}; // 배열
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0 , 2, 4, 6 ...
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식
IntStream intStream = new Random().ints(5); //난수 스트림(크기가 5)
컬렉션이나 배열과 같은 다양한 데이터 소스를 Stream
으로 만들면 똑같은 방식으로 작업을 처리할 수 있다.
Stream 으로 작업을 처리하기
1. 스트림으로 변환
2. 0 ~ N번의 중간 연산
3. 1번의 최종 연산
중간 연산은 여러 번, 최종 연산은 딱 1번 할 수 있다.
데이터소스마다 달랐던 작업 방식을 stream으로 바뀌면서 같은 방식으로 처리할 수 있다.
스트림은 데이터의 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리와 합계, 평균, 카운팅, 최댓값 등의 최종 파이프라인으로 해결한다.
여기서 파이프라인은 컴퓨터 과학에서 한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조
오리지널 스트림 - 중간 처리(필터링 처리, 매핑 처리 등) - 최종 처리
stream.distinct().limit(5).sorted().forEach(System.out.println)
중간 연산 : distinct(), limit(5), sorted()
최종 연산 : forEach(System.out::println) 하나씩 꺼내서 출력..
String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(); // 걸러내기 (중간 연산)
Stream<String> distinctedStream = stream.distinct(); // 중복 제거 (중간 연산)
Stream<String> sortedStream = stream.sort(); // 정렬 (중간 연산)
Stream<String> limitedStream = stream.limit(5); // 스트림 자르기 (중간 연산)
int total = stream.count(); // 요소 개수 세기 (최종 연산)
스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐,
변경하지 않는다
Read OnlyList<Integer> list = Arrays.asList(3, 1, 5, 4, 2); List<Integer> sortedList = list.stream().sorted() // list를 정렬해서 .collect(Collectors.toList()); // 새로운 List에 저장 System.out.println(list); // [3, 1, 5, 4, 2] System.out.println(sortedList); // [1, 2, 3, 4, 5];
스트림은 Iterator처럼
일회용
이다. 필요하면 다시 스트림을 생성해야 함!strStream.forEach(System.out::println); // 모든 요소를 화면에 출력 (최종 연산 - Stream의 요소를 소모, 즉 최종 연산 후에는 스트림이 닫힘) int numOfStr = strStream.count(); // 에러! 스트림이 이미 닫힘~~
최종 연산 전까지 중간 연산이 수행되지 않는다. -
지연된 연산
IntStream intStream = new Random().ints(1, 46); // 1~45 범위의 난수를 발생시키는 무한 스트림. 즉 스트림의 요소의 개수를 가지고 올 수 없다! intStream.distinct().limit(6).sorted() // 중간 연산 .forEach(i->System.out.print(i+",")); // 최종 연산
무한 스트림에서 중복 제거를 할 수 있는 것은 아니지만 이런 코드가 가능하다. 이 Stream을 가지고 어떤 작업을 해야 하는지 체크만 해놨다가, 필요할 때 나중에 수행한다.
이것을 바로 지연된 연산 이라고 함! 지연된 연산 덕분에 이런 코드 작성이 가능하다.
스트림은 작업을 내부 반복으로 처리한다.
for(String str : strList) System.out.println(str);
위 코드를 다음과 같이 바꿀 수 있다. 코드가 매우 간결해짐~
stream.forEach(System.out::println);
forEach
void forEach(Consumer<? super T> action){ Objects.requireNonNull(action); // 매개변수의 널 체크 for(T t : src) // 내부 반복 (for문을 메서드 안으로 넣음) action.accept(T);
스트림의 작업을 병렬로 처리 - 병렬 스트림
스트림을 병렬 스트림으로 변환하는 것.
멀티 쓰레드로 병렬 처리한다는 뜻이다. 람다는 함수형 언어의 특징이라고 했음... 함수형 언어의 특징? 빅데이터 처리하기 용이. 즉 병렬로 처리 하기 용이~Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b"); int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경) .mapToInt(a -> s.length()).sum(); // 모든 문자열의 길이의 합
parallel
로 메서드 호출만 하고 그 다음 작업은 똑같이 처리. 즉 병렬로 처리하여 더 빠르게 결과를 얻을 수 있음
스트림 -> 병렬 스트림, 병렬 스트림에서 스트림으로 변환하는 것은 Sequential()
기본형 스트림 - IntStream, LongStream, DoubleStream
1. 오토 박싱 & 언박싱의 비효율이 제거됨 (Stream<Integer> 대신 IntStream 사용) - 기본형 스트림을 사용하는 이유
- 스트림으로 변환하면 기본형에서 참조형으로 바뀌어서 Stream에 저장된다. ->
오토 박싱
1 -> new Integer(1)- 하지만 데이터가 많으면 이런 처리 하나하나가 시간이 오래 걸려 성능이 느리다. 따라서 Stream<Integer> 대신 IntStream을 사용하면 오토 박싱&언박싱의 비효율이 제거할 수 있다.
항상 사용할 수 있는 것은 아니고, 데이터 소스가기본형
일 때 사용할 수 있다.
- 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공
- T는 어떤 타입인지 모르므로 count 정도만 제공 그러나 IntStream , LongStream 등은 숫자인지 알고 있으므로 sum, average 등의 관련 메서드를 제공한다는 장점이 있다
- 기본형 스트림은 몰라도 되지만.. 성능을 개선하고 싶을 때 이 기본형 스트림을 사용한다.