스트림(Stream)이라는 것은 배열이나 List, Set, Map 같은 컬렉션 대상으로 연산을 수행하는데, 자료의 대상과 관계없이 동일한 방식의 연산으로 요소들의 처리를 쉽고 간단하게 합니다.
컬렉션같은 경우에 stream()
메서드를 확인할 수 있습니다.
여기서 스트림에 대해서 주의할 점은 한번 생성하고 사용한 스트림은 재사용 할 수 없다는 점입니다. 또한, 자료에 대한 스트림을 생성하여 연산을 수행하면 스트림은 소모됩니다. 예를 들어, 배열을 사용하여 요소들의 합을 모두 더했는데, 이 더해진 합들을 다시 이용하여 평균을 구하고자 한다면 스트림을 다시 생성해야 합니다. 즉, 다른 연산을 수행하기 위해서는 스트림을 재생성해야 합니다.
그러면 스트림을 사용한 코드를 한 번 살펴보겠습니다.
for 문과 스트림을 이용한 요소 출력의 결과는 똑같은 것을 확인할 수 있습니다.
public class StreamTest {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
// 기본적으로 for문을 이용한 배열의 요소 출력
for(int val : arr) {
System.out.print(val+" ");
}
System.out.println();
// 스트림을 사용한 배열의 요소 출력
Arrays.stream(arr).forEach(n -> System.out.print(n+" ");
}
}
결과
1 2 3 4 5
1 2 3 4 5
위 코드에서 Arrays
는 배열을 다루기 위한 다양한 메소드가 포함되어 있습니다. 거기에 Arrays.stream(arr)
은 IntStream을 반환하게 됩니다. 즉, IntStream is = Arrays.stream(arr);
와 같습니다.
또한 forEach()
는 배열의 요소를 하나씩 꺼내겠다는 의미이고, 내부에는 람다식으로, n은 배열의 각 요소를 n으로 대입하여 출력하겠다는 의미입니다.
스트림 연산은 중간 연산과 최종 연산으로 구분 됩니다.
스트림에 대해 중간 연산은 여러 개의 연산이 적용될 수 있지만 최종 연산은 마지막에 한 번만 적용됩니다.
최종연산이 호출되어야 중간 연산에 대한 수행이 이루어 지고 그 결과가 만들어집니다.
따라서 중간 연산에 대한 결과를 연산 중에 알 수 없으며
이를 지연 연산이라 함
중간 연산의 종류는 조건에 맞는 요소를 추출(filter) 또는 요소를 변환(map)하는 것과 정렬(sorted) 등이 있습니다. 최종 연산이 호출될 때 중간 연산이 수행되고 결과가 생성됩니다.
다음과 같은 코드는 스트림을 이용하여 String List에서 문자열의 길이가 5 이상인 요소만 출력하는 코드입니다.
sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
이 코드에서 filter()
는 중간 연산이며, forEach()
는 최종 연산입니다.
다음 코드는 고객 클래스 배열에서 고객 이름만 가져오는 코드입니다.
customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));
위의 코드에서 map()
은 중간 연산이고, forEach()
는 최종 연산입니다.
최종 연산은 스트림이 관리하는 자료를 하나씩 소모해가며 연산이 수행 되며, 최종 연산 후에 스트림은 더 이상 다른 연산을 적용할 수 없습니다.
이 최종 연산의 종류는 forEach(), count(), sum() 등이 존재하며, 각각의 메서드가 하는 역할을 요소를 하나씩 꺼내오고, 요소의 개수, 요소의 합을 반환합니다.
위 같이 정의된 연산 만으로 원하는 결과를 얻을 수 없을 때, 개발자가 직접 구현한 연산을 적용할 수 있는 reduce() 연산이 존재합니다.
선언된 reduce() 연산은 다음과 같습니다.
T reduce(T identify, BinaryOperator<T> accumulator)
첫 번째 파라미터인 T identify
는 기본 값을 의미하고 두 번째 파라미터인 BinaryOperator<T> accumulator
는 BinaryOperator 인터페이스를 구현한 부분이며, 람다식에 따라 다양한 기능을 수행할 수 있습니다. 람다식이 길다면, BinaryOperator를 상속한 클래스를 구현하시면 됩니다.
배열의 모든 요소의 합을 구하는 reduce() 연산을 구현하면 다음과 같습니다.
Arrays.stream(arr).reduce(0, (a,b)->a+b));
여기서, 람다식이 길어 BinaryOperator를 구현하실 때, apply()
라는 메서드를 재정의하여 구현해야 합니다. 이 apply()
메서드의 두 개의 인자를 받아 반복해서 수행됩니다.
BinaryOperator를 구현하여 배열에 여러 문자열이 있을 때 길이가 가장 긴 문자열 찾는 코드는 다음과 같이 작성할 수 있습니다.
class CompareString implements BinaryOperator<String>{
@Override
public String apply(String s1, String s2) {
if (s1.getBytes().length >= s2.getBytes().length) return s1;
else return s2;
}
}
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
// 두 번째 파라미터에 람다식을 이용
System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)->
{if (s1.getBytes().length >= s2.getBytes().length)
return s1;
else return s2;}));
//BinaryOperator를 구현한 클래스 이용
String str = Arrays.stream(greetings).reduce(new CompareString()).get();
System.out.println(str);
}
}
결과
안녕하세요~~~
안녕하세요~~~
이상으로 자바에서 사용하는 스트림에 대해서 간단히 알아봤습니다.