본문 바로가기
IT 개인 공부/Java

[Java] 람다(Lambda) & 스트림(Stream)(2)

by Libi 2021. 7. 12.
반응형

이번에는 스트림(Stream)에 대해서 배워보자(JDK 8에 새롭게 추가된 것이기 때문에 I/O Stream 과는 다른 개념이다).

스트림은 흐르는 시냇물을 뜻한다. 이처럼 자바에서 스트림은 컬렉션, 배열 등에 저장된 데이터들을 처리할 때 매번 따로 처리하지 않고 물 흘러가듯이 함수형 인터페이스(람다식)를 활용하여 연속적으로 처리해 주는 기능을 뜻한다.

이게 무슨 말인지 이해가 안 갈 수도 있으니 간단한 예제를 통해 이해해 보자.

양의 정수를 가지는 리스트가 존재한다고 하자. 리스트에 10보다 큰 정수가 몇 개 존재하는지를 알기 위해서는 어떻게 해야 할까? 일반적으로는 for 문을 이용하여 리스트를 순차적으로 탐색하며 개수를 카운트할 것이다.

List<Integer> numbers = Arrays.asList(1, 10, 3, 5, 11, 7, 8, 22);
long cnt = 0;
for (int n : numbers) {
	if (n > 10) {
		cnt++;
	}
}
System.out.println("10보다 큰 수의 개수 : " + cnt);

 

하지만 스트림을 이용한다면 단 한 줄로 for 문과 같은 결과를 얻을 수 있다.

List<Integer> numbers = Arrays.asList(1, 10, 3, 5, 11, 7, 8, 22);
long cnt = 0;
cnt = numbers.stream().filter(i -> i > 10).count(); //filter에 람다식을 적용
System.out.println("10보다 큰 수의 개수 : " + cnt);

 

스트림을 이용한 코드가 훨씬 간편하고 직관적이게 느껴지지 않는가?

지금 사용된 문법을 모른다 하여도 대충 이해해 보면 numbers 리스트를 스트림 형태로 만들어서 10보다 큰 수를 필터링한 결과를 count 하라는 의미로 해석할 것이다.

이처럼 코드의 가독성이 좋아지는 것이 스트림을 사용하는 이유이다. 또한, 스트림은 간단하게 병렬 처리가 가능하다는 장점도 있다.

스트림의 특징은 다음과 같다.

  • 스트림은 원본 데이터를 변경할 수 없다. 필요하다면 변경 결과를 다른 컬렉션이나 배열에 담아서 반환한다.
  • 스트림은 일회용이다. 람다식을 사용하는 이유 중 하나가 일회용이었다. 스트림 역시 마찬가지로 한 번만 사용이 가능하다. 만약 여러 번 필요하다면 스트림을 다시 생성하거나 별도의 메서드를 작성해야 한다.
  • ​스트림은 작업을 내부 반복으로 처리한다. 여러 개의 연산이 존재할 때 스트림의 모든 원소가 각 연산을 함께 실행하는 것이 아니라 각 원소마다 개별적으로 연산을 진행한다. 따라서 연산 순서를 적절하게 배치하는 것이 중요하다. 또한 덕분에 병렬로 처리하거나 최적화된 순서로 처리할 수 있게 된다.

 

그렇다면 스트림의 문법에 대해서 알아보자. 스트림의 전체 동작 과정은 스트림 생성 후 연산하여 결과를 출력하는 것이다. 하나씩 천천히 살펴보도록 하자.

먼저 스트림 생성이다. 위의 코드를 보면서 대충 파악했겠지만 stream() 메서드를 통해 컬렉션이나 배열을 스트림으로 만들 수 있다. 또한, 원하는 데이터를 가지는 스트림을 직접 생성할 수도 있다.

String[] str = {"A", "B", "C"};
Stream<String> streamArr = Arrays.stream(str);
		
List<Integer> numbers = Arrays.asList(1, 10, 3, 5, 11, 7, 8, 22);
Stream<Integer> streamList = numbers.stream();
		
Stream<String> streamBuilder = Stream.<String>builder().add("A").add("B").add("C").build();

 

스트림이 생성되었으면 원하는 연산을 통해 결과를 얻어내야 한다. 연산은 크게 중간 연산과 최종 연산으로 나눌 수 있다.

중간 연산은 파이프라이닝 할 수 있는 스트림 연산을 뜻한다. 파이프라이닝이란 명령어를 중첩하여 실행하는 기술을 의미한다. 이처럼 중간 연산에서 일어나는 연산의 결과들은 그다음 연산으로 이어지기 때문에 결과를 출력하는 등 스트림을 닫는 연산을 해서는 안 된다.

그렇다면 자연스럽게 최종 연산은 중간 연산을 통해 스트림을 원하는 방식으로 처리한 결과를 마무리 짓는 연산을 의미한다. 최종 연산이 끝나면 스트림은 닫히게 되고 더 이상 사용할 수 없게 된다. 그렇다면 어떤 함수들이 있는지 알아보자.

먼저 중간 연산의 몇 가지 함수들을 알아보자. 아래의 리스트는 각 함수의 결과를 보여주기 위해 사용될 예시이다.

List<String> numbers = Arrays.asList("abC", "bbe", "44", "Hello", "b2aX", "123");

 

이번엔 최종 연산의 몇 가지 함수들을 알아보자. 아래의 리스트는 각 함수의 결과를 보여주기 위해 사용될 예시이다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

 

반응형

댓글