스트림으로 작업 하려면, 스트림이 필요하니까 일단 스트림을 생성하는 방법부터 먼저 시작하자. 

스트림의 소스가 될 수 있는 대상은 배열, 컬렉션, 임의의 수 등 다양하며, 

이 다양한 소스들로 부터 스트림을 생성하는 방법에 대해 배우게 될 것이다.



*컬렉션

컬렉션의 최고 조상인 Collection에 stream()이 정의되어 있다. 그래서 Collection의 자손인 List와 Set을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.

stream()은 해당 컬렉션을 소스(source)로 하는 스트림을 반환한다.


Stream<T> Collection.stream()


예를 들어 List로 부터 스트림을 생성하는 코드는 다음과 같다.

List<Integer> list = Arrays.asList(1,2,3,4,5); //가변인자

Stream<Integer> intStream = list.stream(); //list를 소스로 하는 컬렉션 생성


forEach()는 지정된 작업을 스트림의 모든 요소에 대해 수행한다. 아래의 문장은 스트림의 모든 요소를 화면에 출력한다.

intStream.forEach(System.out::println); //스트림의 모든 요소를 출력한다.
intStream.forEach(System.out::println); //에러. 스트림이 이미 닫혔다.


한 가지 주의할 점은 forEach(0가 스트림의 요소를 소모하면서 작업을 수행하므로 같은 스트림 forEach()를 두 번 호출할 수 없다는 것이다. 그래서 스트림의 요소를 한번 더 출력하려면 스트림을 새로 생성해야 한다. forEach()에 의해 스트림의 요소가 소모되는 것이지, 소스의 요소가 소모되는 것은 아니기 때문에 같은 소스로 부터 다시 스트림을 생성할 수 있다.


forEach()에 대한 것은 나중에 더 자세히 배우기로 하고, 지금은 forEach()로 스트림의 모든 요소를 화면에 출력하는 방법만 알아두자.



*배열

배열을 소스로 하는 스트림을 생성하는 메서드는 다음과 같이 Stream과 Arrays에 static 메서드로 정의되어 있다.


Stream<T> Stream.of(T...values) //가변인자

Stream<T> Stream.of(T[]) 

Stream<T> Arrays.stream(T[])

Stream<T> Arrays.stream(T[] array, 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);


이 외에도 long과 double타입의 배열로 부터 LongStream과 DoubleStream을 반환하는 메서드들이 있지만 일일이 나열하지 않아도 쉽게 유추해낼 수 있을 것으로 생략한다.


*특정 범위의 정수

IntStream과 LongStream은 다음과 같이 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있다.


IntStream IntStream.range(int begin, int end)

IntStream IntStream.rangeClosed(int begin, int end)


range()의 경우 경계의 끝인 end가 범위에 포함되지 않고, rangeClosed()의 경우는 범위가 포함된다.

IntStream intStream = IntStream.range(1,5); //1,2,3,4

IntStream intStream = IntStream.rangeClosed(1,5); //1,2,3,4,5


int보다 큰 범위의 스트림을 생성하려면 LongStream에 있는 동일한 이름의 메서드를 사용하면 된다.



*임의의 수

난수를 생성하는데 사용하는 Random클래스에는 아래와 같이 인스턴스 메서드들이 포함되어 있다. 이 메서드들은 해당 타입의 난수들로 이루어진 스트림을 반환한다.


IntStream ints()

LongStream longs()

DoubleStream doubles()


이 메서드들이 반환하는 스트림은 크기가 정해지지 않은 '무한 스트림(infinited stream)'이므로 limit()도 같이 사용해서 스트림의 크기를 제한해 주어야 한다. limit()은 스트림의 개수를 지정하는데 사용되며, 무한 스트림을 유한 스트림으로 만들어 준다.

IntStream     intStream = new Random().ints();     //무한 스트림

intStream.limit(5).forEach(System.out::println); //5개의 요소만 출력한다.

아래의 메서드들은 매개변수로 스트림의 크기를 지정해서 '유한 스트림'을 생성해서 반환하므로 limit()을 사용하지 않아도 된다.


IntStream ints(long streamSize)

LongStream longs(long streamSize)

DoubleStream doubles(long streamSize)


IntStream intStream = new Random().ints(5); //크기가 5인 난수 스트림을 반환

위 메서드들에 의해 생성된 스트림 난수는 아래의 범위를 갖는다.


Integer.MIN_VALUE <= ints()         <= Integer.MAX_VALUE

Long.MIN_VALUE <= longs()     <=Long.MAX_VALUE

0.0                <= doubles()      < 1.0


지정된 범위(begin~end)의 난수를 발생시키는 스트림을 얻는 메서드는 아래와 같다. 단, end는 범위에 포함되지 않는다.


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)

*람다식-  iterator(), generate()

Stream클래스의 iterate()와 generate()는 람다식을 매개변수로 받아서, 이 람다식에 의해 계산된 결과를 다시 seed값으로 해서 계산을 반복한다. 아래의 evenStream은 0부터 시작해서 값이 2씩 계속 증가한다.


Stream<Integer> evenStream = Stream.iterate(0,n->n+2); //0,2,4,6, ...


n -> n+2

0 -> 0+2

2 -> 2+2

4 -> 4+2


generate()도 iterate()처럼, 람다식에 의해 계산되는 값을 요소로 하는 무한 스트림을 생성해서 반환하지만, iterator()와 달리, 이전 결과를 이용해서 다음 요소를 계산하지 않는다.

Stream<Double> randomStream = Stream.generate(Math::random);

Stream<Integer> oneStream = Stream.generate(()->1);


그리고 generate()에 정의된 매개변수의 타입은 Supplier<T>이므로 매개변수가 없는 람다식만 허용된다. 한 가지 주의할 점은 iterate()와 generate()에 의해 생성된 스트림을 아래와 같이 기본형 스트림 타입의 참조 변수로 다룰수 없다는 것이다.


IntStream evenStream = Stream.iterate(0,n->n+2); //에러

DoubleStream randomStream = Stream.generate(Math::random); //에러


굳이 필요하다면, 아래와 같이 mapToInt()와 같은 메서드로 변환을 해야 한다.

IntStream evenStream = Stream.iterate(0, n->n+2).mapToInt(Integer::valueOf);

Stream<Integer> stream = evenStream.boxed(); //IntSteam -> Stream<Integer>


반대로 IntStream타입의 스트림을 Stream<Integer>타입으로 변환하려면, boxed()를 사용하면 된다. 스트림간의 변환에 대해서는 나중에 같이 다룰 것이므로, 지금은 참고만 하자.


*스트림의 변환에 관한 표


스트림 타입 변환 표


스트림 타입 변환 정리표

From

To

변환 메소드

스트림 -> 기본형 스트림

Stream<T>

IntStream

LongStream

DoubleStream

MapToInt (Function<T> mapper)

mapToLong (ToLongFunction<T> mapper)

mapToDouble(ToDoubleFunction<T>mapper)

기본형 스트림 -> 스트림

IntStream

LongStream

DoubleStream

Stream<Integer>

Stream<Long>

Stream<Double>

boxed()

Stream<U>

mapToObj (DoubleFunction<U> mapper)

기본형 스트림 -> 기본형 스트림

Stream<T>

IntStream

LongStream

DoubleStream

asLongStream()

asDoubleStream()

스트림 -> 부분 스트림

Stream<T>

IntStream

Stream<T>

IntStream

skip (long n)

limit (long maxSize)

두 개의 스트림 -> 스트림

Stream<T>, Stream<T>

Stream<T>

concat (Stream<T> a, Stream<T> b)

IntStream, IntStream

IntStream

concat (IntStream a, IntStream b)

LongStream, LongStream

LongStream

concat (LongStream a, LongStream b)

DoubleStream, DoubleStream

DoubleStream

concat (DoubleStream a, DoubleStream b)

스트림의 스트림 -> 스트림

Stream<Stream<T>>

Stream<T>

flatMap (Function mapper)

Stream<IntStream>

IntStream

flatMapToInt (Function mapper)

Stream<LongStream>

LongStream

flatMapToLong (Function mapper)

Stream<DoubleStream>

DoubleStream

flatMapToDouble (Function mapper)

스트림 <-> 병렬 스트림

Stream<T>

IntStream

LongStream

DoubleStream

Stream<T>

IntStream

LongStream

DoubleStream

parallel() // 스트림->병렬 스트림

sequential() // 병렬 스트림 -> 스트림

스트림 -> 컬렉션

Stream<T>

IntStream

LongStream

DoubleStream

Collection<T>

collect (Collectors.toCollection (Supplier factory))

List<T>

collect (Collectors.toList())

Set<T>

collect (Collectors.toSet())

컬렉션 -> 스트림

Collection<T>, List<T>, Set<T>

Stream<T>

stream()

스트림 -> Map

Stream<T>

IntStream

LongStream

DoubleStream

Map<K,V>

collect(Collectors.toMap(Function key, Function value))

collect(Collectors.toMap(Function key, Function value, BinaryOperator merge))

collect(Collectors.toMap(Function key, Function value, BinaryOperator merge, Supplier mapSupplier))

스트림 -> 배열

Stream<T>

Object[]

toArray()

T[]

toArray(IntFunction<A[]> generator)

IntStream

LongStream

DoubleStream

int[]

long[]

double[]

toArray()


*파일

 java.nio.file.Files는 파일을 다루는데 필요한 유용한 메서드들을 제공하는데, list()는 지정된 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환한다.

참고)Path는 하나의 파일 또는 경로를 의미한다.

Stream<Path> Files.list(Path dir)

이 외에도 Files클래스에는 Path를 요소로 하는 스트림을 생성하는 메서드가 더 있지만, 이 장의 주제를 벗어나므로 생략,

그리고, 파일의 한 행(line)을 요소로 하는 스트림을 생성하는 메서드도 있다. 아래의 세번째 메서드는 BufferedReader클래스에 속한 것인데, 파일 뿐만 아니라 다른 입력대상으로부터도 데이터를 행단위로 읽어올 수 있다.


Stream<String>    Files.lines(Path path)

Stream<String>    Files.lines(Path path, Charset cs)

Stream<String>    lines()         //BufferedReader클래스의 메서드


*빈 스트림

요소가 하나도 없는 비어있는 스트림을 생성할 수도 있다. 스트림에 연산을 수행한 결과가 하나도 없을 떄, null보다 빈 스트림을 반환하는 것이 낫다.


Stream emptyStream = Stream.empty(); //empty()는 빈 스트림을 생성해서 반환한다.

long count = emptyStream.count(); //count의 값은 0


count()는 스트림 요소의 개수를 반환하며, 위의 문장에서 변수 count의 값이 0이 된다.


*두 스트림의 연결

Stream의 static메서드인 concat()을 사용하면, 두 스트림을 하나로 연결할 수 있다. 물론 연결하려는  두 스트림의 요소는 같은 타입이어야 하낟.


String[] str1 = {"123","456","789"};

String[] str = {"ABC","abc","DEF"};


Stream<String> strs1 = Stream.of(str1);

Stream<String> strs2 = Stream.of(str2);

Stream<String> strs3 = Stream.concat(strs1,strs2); //두 스트림을 하나로 연결



+ Recent posts