[기본 개념] 9 | (2.1) 스트림
[기본 개념] 9 | (2.1) 스트림
1> 스트림이란?
2> 스트림 만들기
3 스트림의 중간연산
4 Optional<T>와 OptionalInt
5 스트림의 최종연산
6 collect( )
7 Collector 구현하기
8 스트림의 변환
1. 스트림이란?
스트림은 데이터 소스를 같은 방식으로 다룰 수 있게 하고, 자주 사용되는 메서드를 정의해 놓았다.
스트림은 데이터 소스를 변경하지 않는다.
스트림은 데이터를 읽기만 할 뿐 변경하지 않는다.
스트림은 일회용이다.
스트림도 Iterator처럼 한번 사용하면 닫혀서 다시 사용할 수 없다. 필요하다면 다시 생성해야 한다.
strStream1.sorted( ).forEach(System.out :: println) ;
int numOfStr = strStream1.count( ) ; // error. 스트림이 이미 닫혔음
스트림은 작업을 내부 반복으로 처리한다.
내부 반복이란 반복문을 메서드의 내부에 숨길 수 있다는 것이다.
forEach( )는 메서드 안으로 for문을 넣은 것이다. 수행할 작업은 매개변수로 받는다.
스트림의 연산
스트림이 제공하는 다양한 연산으로 간단히 처리할 수 있다.
제공하는 연산은 중간 연산과 최종 연산으로 분류된다.
중간 연산 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산을 할 수 있음
최종 연산 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 한 번만 가능
stream.distinct( ).limit(5).sorted( ).forEach(System.out :: println) ;
: 중간 연산 : 최종 연산
지연된 연산
최종 연산이 수행되기 전까지 중간 연산이 수행되지 않는다. 최종 연산이 수행되어야 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.
Stream<Integer>와 IntStream
요소의 타입이 T인 스트림은 기본적으로 Stream<T>이지만, 오토박싱&언박싱으로 인한 비효율을 줄이기 위해 데이터 소스의 요소를 기본형으로 다루는 스트림, IntStream, LongStream, DoubleStream이 제공된다.
병렬 스트림
스트림으로 데이터를 다루면 병렬 처리가 쉽다. 병렬 스트림은 스트림에 parallel( )이라는 메서드를 호출하면 된다. 반대로 parallell( ) 호출을 취소하려면 sequential( )을 호출하면 된다.
병렬 처리가 항상 더 빠른 결과를 얻는 것은 아니다.
2. 스트림 만들기
컬렉션
컬렉션의 최고 조상인 Collection에 stream( )이 정의되어 있다. 그래서 Collection의 자손인 List, Set을 구현한 컬렉션 클래스들 생성할 수 있다.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5) ; // 가변인자
Stream<Integer> intStream = list.stream( ) ; // list를 소스로 하는 컬렉션 생성
배열
배열을 소스로 하는 스트림을 생성하는 메서드는 Stream과 Arrays에 static메서드로 정의되어 있다.
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) ;
그리고 int, long, double과 같은 기본형 배열을 소스로 하는 스트림을 생성하는 메서드도 있다.
특정 범위의 정수
IntStream과 LongStream은 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range( )와 rangeClosed( )를 가지고 있다.
range( )의 경우, 경계의 끝인 end가 범위에 포함되지 않고, rangeClosed( )의 경우는 포함된다.
IntStream intStream = IntStream.range(1, 5) ; // 1, 2, 3, 4
IntStream intStream = IntStream.rangeClosed(1, 5) ; // 1, 2, 3, 4, 5
임의의 수
아래 메서드들은 해당 타입의 난수들로 이뤄진 스트림을 반환한다.
IntStream ints( )
LongStream longs( )
DoubleStream doubles( )
이 메서드들이 반환하는 스트림은 '무한 스트림'이다. 따라서 limit( )와 같이 사용해서 스트림의 크기를 제한하여 유한 스트림으로 만들어 줘야 한다.
IntStream intStream = new Random( ).ints( ) ; // 무한 스트림
intStream.limit(5).forEach(System.out :: println) ; // 5개의 요소만 출력한다.
매개변수로 크기를 지정해서 유한 스트림을 생성하여 반환할 수도 있다.
IntStream intStream = new Random( ).ints(5) ; // 크기가 5인 난수 스트림을 반환
람다식 - iterate( ), generate( )
iterate( )와 generate( )은 람다식을 매개변수로 받아 계산되는 값을 요소로 하는 무한 스트림을 생성한다.
iterate( )은 씨앗값으로 지정된 값부터 람다식에 의해 계산된 결과를 다시 seed값으로 해서 계산을 반복한다.
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2) ; // 0, 2, 4, 6, . . .
generate( )은 iterate( )와 달리 이전 결과를 이용해서 다음 요소를 계산하지 않는다. 그리고 매개변수의 타입은 Supplier<T>이므로 매개변수가 없는 람다식만 허용된다.
Stream<Double> randomStream = Stream.generate(Math :: random) ;
Stream<Integer> oneStream = Stream.generate(( ) -> 1) ;
iterate( )와 generate( )에 의해 생성된 스트림을 기본형 스트림 타입의 참조변수로 다룰 수 없다.
파일
java.nio.file.Files는 파일을 다루는데 필요한 메서드를 제공하는 데, list( )는 지정된 디렉토리에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환한다.
파일의 한 행을 요소로 하는 스트림을 생성하는 메서드도 있다.
빈 스트림
요소가 하나도 없는 비어있는 스트림을 empty( )로 생성할 수 있다.
두 스트림의 연결
두 스트림의 요소가 같은 타입일 때, concat( )을 이용하면 두 스트림을 하나로 연결할 수 있다.
출처 | Java의 정석 (남궁 성)