스트림(Stream) API
기존의 자바에서는 많은 양의 데이터를 저장하기 위해 배열이나 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하기 위해 컬렉션을 정의해 사용하였다.
컬렉션은 데이터를 저장하는 자료 구조에 따라 핵심이 되는 주요 인터페이스List, Map, Set을 정의하였지만 결국 성격이 다른 자료구조는 사용 방법이 달라 결국 반쪽 짜리 표준화라고 볼 수 있고 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성해야 했다.
이렇게 작성된 코드는 가독성도 떨어지며, 코드의 재사용이 거의 불가능하며 데이터마다 다른 방법으로 접근해야했다.
이러한 문제점을 극복하기 위해서 Jdk 1.8부터 스트림(stream) API가 도입되었다. 스트림(Stream)API란 다양한 데이터 소스(컬렉션, 배열 등)를 표준화된 방법으로 다루기 위한 것을 말한다.
스트림(Stream) API를 이용하면 배열이나 컬렉션와 같은 데이터소스를 같은 방법으로 다룰 수 있게된다.
- 모든 종류의 컬렉션을 stream() 메서드를 통해 쉽게 바꿀 수 있다.
Stream<T> Collection.stream() // Collection에 정의된 stream()메서드 이용
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); // 컬렉션.
Stream<String> 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)
스트림 API의 동작 흐름
스트림 API는 세 가지 단계에 걸쳐서 동작한다.
1. 스트림 생성
2. 중간 연산 – 연산결과가 스트림인 연산. 반복적으로 적용가능(o~n번)
3. 최종 연산 – 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 적용가능(0~1번)
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(); // 요소 개수 세기(최종연산)
스트림의 특징
- 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.
List<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); // 모든 요소를 화면에 출력(최종연산)
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<String> strStream = Stream.of("dd","aaa","CC","cc","b");
int sum = strStream.parallelStream() // 병렬 스트림으로 전환(속성만 변경)
.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
- 기본형 스트림(IntStream, LongStream, DoubleStream)
- 오토박싱&언박싱의 비효율이 제거됨(Stream<Integer> 대신 IntStream사용)
- 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공
스트림 만들기
컬렉션
- Collection인터페이스의 stream()으로 컬렉션을 스트림으로 변환해준다.
Stream<E> stream() // Collection 인터페이스의 메서드
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); // list를 스트림으로 변환
// 스트림의 모든 요소 출력
intStream.forEach(System.out::print); // 12345
intStream.forEach(System.out::print); // 에러. 스트림이 이미 닫혔다.
배열
- 객체 배열로부터 스트림 만들기
Stream<T> Stream.of(T....values); // 가변 인자
Stream<T> Stream.of(T[]);
Stream<T> Arrays.stream(T[]);
Stream<T> Arrays.stream(T[], int startInclusive, int endExclusive);`//배열의 일부만으로 생성
// 예시
Stream<String> strStream = Stream.of("a","b","c"); // 가변 인자
Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);
- 기본형 배열로부터 스트림 생성하기( IntStream, LongStream, DoubleStream )
IntStream IntStream.of(int....values); // 가변 인자
IntStream IntStream.of(int[]);
IntStream Arrays.stream(int[]);
IntStream Arrays.stream(int[], int startInclusive, int endExclusive);`//배열의 일부만으로 생성
// 예시
IntStream intstream = IntStream.of(new int[] {1,2,3,4});
IntStream intstream = Arrays.stream(new int[] {1,2,3,4});
intstream.forEach(System.out::print);
- 숫자와 관련된 유용한 메서드를 Stream보다 더 많이 제공
System.out.println(intstream.average());
// sum(), average() 와 같은 숫자와 관련된 메서드 제공
// Stream은 숫자외에도 여러 타입의 스트림이 가능해야 하므로 넣지 않음
임의의 수
- 난수를 요소로 갖는 스트림 생성하기
IntStreamintStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력한다.
IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림을 반환
스트림은 유한 스트림과 무한 스트림 두가지가 있다. ints(), longs(), doubles()은 랜덤 클래스에 정의되어 있는 메서드로 각 타입의 범위안에 속하는 난수들을 요소로 갖는 무한 스트림을 생성한다.생성된 무한 스트림을 limet(5)과 같은 메서드를 통해 잘라서 사용하거나 ints(5)와 같이 크기를 지정해야 한다.
- 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드
IntStream ints(int begin, int end) // 무한 스트림
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, double end)
IntStream ints(long streamSize, int begin, int end) // 유한 스트림
LongStream longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)
특정 범위의 정수
- 특정 범위의 정수를 요소로 갖는 스트림 생성하기(IntStream, LongStream)
IntStream IntStream.range(int begin, int end) // 이상, 미만
IntStream IntStream.rangeClosed(int begin, int end) // 이상, 이하
// 예시
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
IntStream intStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5
람다식
- 람다식을 소스로 하는 스트림 생성하기
// 초기값, 람다식
static <T> Stream<T> iterate(T seed,UnaryOperator<T> f) // 이전 요소에 종속적, 무한 스트림
static <T> Stream<T> generate(Supplier<T> s) // 이전 요소에 독립적
- iterate()는 이전 요소를 seed로 해서 다음 요소를 계산한다.
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,6, ...
- generate()는 seed를 사용하지 않는다.
Stream<Double> randomStream = Stream.generate(Math::random);
Stream<Integer> oneStream = Stream.generate(()->1); // 1,1,1,1, ...
파일과 빈 스트림
- 파일을 소스로 하는 스트림 생성하기
Stream<Path> Files.list(Path dir) // Path는 파일 또는 디렉토리
Stream<String> Files.lines(Path path)
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클래스의 메서드
- 비어있는 스트림 생성하기
Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환한다.
long count = emptyStream.count(); // count의 값은 0
'Java' 카테고리의 다른 글
[JAVA] 스트림(Stream) API(3, 최종 연산) (0) | 2024.03.09 |
---|---|
[JAVA] 스트림(Stream) API(2, 중간 연산, Optinal<T>) (0) | 2024.02.29 |
[JAVA] 람다 표현식(Lambda Expression) (0) | 2024.01.28 |
[JAVA] 표준 입출력과 File 클래스, 직렬화(Serialization) (0) | 2024.01.18 |
[JAVA] 버퍼 스트림 (0) | 2024.01.18 |