스트림
스트림이란?
자료가 모여있는 배열이나 컬렉션 또는 특정 범위안에 있는 일련의 숫자를 처리하는 기능이 미리 구현되어 있다면 프로그램의 코드가 훨씬 간결해지고 일관성 있게 다룰 수 있겠죠? 예를 들어 배열 요소를 특정 기준에 따라 정렬(sorting)하거나, 요소 중 특정 값은 제외하고 출력하는 (filter_ 기능처럼 말입니다. 이렇게 여러 자료의 처리에 대한 기능을 구현해 놓은 클래스가 스트림(stream) 입니다. 스트림을 활용하면 배열, 컬렉션 등의 자료를 일관성 있게 처리할 수 있습니다.
Collection에서 스트림 생성하고 사용하기
스트림의 특징
자료의 대상과 관계없이 동일한 연산을 수행한다.
배열이나 컬렉션에 저장된 자료를 가지고 수행할 수 있는 연산은 여러 가지가 있습니다. 배열에 저장된 요소 값을 출력한다든지, 조건에 따라 자료를 추철하거나, 자료가 숫자일 때 합계,평균 등을 구할 수도 있습니다. 스트림은 컬렉션의 여러 자료 구조에 대해 이러한 작업을 일관성 있게 처리할 수 있는 메소드를 제공합니다.
한 번 생성하고 사용한 스트림은 재사용 할 수 없다.
어떤 자료에 대한 스트림을 생성하고 이 스트림에 메소드를 호출하여 연산을 수행했다면 해당 스트림을 다시 다른 연산에 사용할 수 없습니다. 예를 들어 스트림을 생성하여 배열에 있는 요소를 출력하기 위해 각 요소들을 하나씩 순회하면서 출력에 사용하는데, 이때 요소들이 '소모된다'고 이야기합니다. 소모된 요소는 재사용할 수 없습니다. 만약 다른기능을 호출하려면 스트림을 새로 생성해야 합니다.
스트림의 연산은 기존 자료를 변경하지 않는다.
스트림을 생성하여 정렬한다거나 합을 구하는 등의 여러 연산을 수행한다고 해서 기존 배열이나 컬렉션이 변경되지는 않습니다. 스트림 연산을 위해 사용하는 메모리 공간이 별도로 존재하므로, 스트림의 여러 메소드를 호출하더라도 기존 자료에는 영향을 미치지 않습니다.
스트림의 연산은 중간 연산과 최종 연산이 있다
스트림에서 사용하는 메소드는 크게 중간 연산과 최종 연산 두 가지로 나뉩니다. 스트림에 중간 연산은 여러 개가 적용될 수 있고, 최종 연산은 맨 마지막에 한 번 적용됩니다. 만약 중간 연산이 여러 개 호출되었더라도 최종 연산이 호출되어야 스트림의 중간 연산이 모두 적용됩니다. 예를 들어 자료를 정렬하거나 검색하는 중간 연산이 호출되어도 최종 연산이 호출되지 않으면 정렬이나 검색한 결과를 가져올 수 없습니다. 이를 '지연 연산(lazy evaluation)' 이라고 합니다.
프로그래머가 기능을 지정하는 reduce() 연산
JDK에서 제공하는 reduce()메소드의 정의는 다음과 같습니다.
T reduce(T identify, BivaryOperator<T> accumulator)
첫 번쨰 매개변수 T identify는 초깃값을 의미하고 두 번떄 매개변수 BinaryOperator<T> accumulator는 수행해야 할 기능입니다. BinaryOperator 인터페이스는 두 매개변수로 람다식을 구현하며 이 람다식이 각 요소가 수행해야 할 기능이 됩니다. 이떄 BinaryOperator 인터페이스를 구현한 람다식을 직접 써도 되고, 람다식이 길면 인터페이스를 구현한 클래스를 생성하여 대입해도 됩니다. 또한 BinaryOperator는 함수형 인터페이스로 apply() 메소드를 반드시 구현해야 합니다. apply() 메소드는 두 개의 매개변수와 한 개의 반환 값을 가지는데, 세 개 모두 같은 자료형입니다. reduce() 메소드가 호출될 떄 BinaryOperator의 apply() 메소드가 호출됩니다.
reduce() 메소드를 사용해 모든 요소의 합을 구할 떄, 두 번쨰 매개변수에 람다식을 직접 쓰는 경우는 다음과 같습니다.
Arrays.stream(arr).reduce(0, (a, b) -> a + b));
17~20행에서는 reduce() 메소드 내에 직접 람다식을 구현하였습니다. 람다식을 살펴보면 문자열을 비교하여 바이트 수가 더 긴 문자열을 반환합니다. 내부적으로 이 람다식 부분이 요소 개수만큼 반복해서 호출되고 결과적으로 가장 긴 문자열을 반환합니다. 구현하는 람다식이 너무 긴 경우에는 6~12행과 같이 직접 BinaryOperator 인터페이스를 구현한 클래스를 만들고 reduce() 메소드에 해당 클래스로 생성한 인스턴스를 매개 변수로 전달하면 여기에 구현된 apply() 메소드가 자동으로 호출됩니다.