람다, 스트림은 자바 8에서 도입되면서 자바를 확실히 버전업 시킨 중요한 기능이었고 현재까지도 자바의 함수형 프로그래밍 개념에 가장 중요한 부분이 된다. 람다, 스트림을 통해 코드가 간결해지고 효율적이 되지만 반면에 몇 가지 주의할 점이 존재한다. 자바로 데이터 처리라든가 복잡한 로직을 코딩할 때 경험한 사례에서 이러한 주의할 점을 정리해봤다.

람다, 스트림 개요

일단 람다, 스트림에 대해 복습해보자. 2014년에 자바 8이 나왔는데 아직 람다, 스트림을 모르고 있었다면 생각보다 간단하니 이번 기회에 정리해보자.

람다는 간단히 말하자면 이름 없이 함수를 정의하는 구문 형태를 말한다.

() -> System.out.println("Hello World")

원래 자바에서 모든 기능 실행은 클래스 메서드 단위였는데 구현해야 할 클래스 메서드가 하나인 클래스는 람다를 통해 익명 클래스를 선언하는 방식이 훨씬 간단하게 된다.

// 람다를 사용하지 않은 경우 - 익명 클래스 선언
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
runnable.run();

// 람다를 사용한 경우 - 익명 클래스 선언이 간단해짐
Runnable runnable = () -> System.out.println("Hello World");
runnable.run();

람다는 함수형 프로그래밍의 핵심 개념으로 함수를 변수처럼 사용할 수 있고 함수를 인자로 전달하거나 함수를 반환할 수 있다.

스트림은 배열형(컬렉션) 데이터를 함수형으로 처리하기 위한 API다. 이를 통해 데이터를 필터링하거나 정렬하거나 집계하는 등의 작업을 훨씬 간결하면서 풍요롭게 코딩할 수 있다. 예를 들어 정수 리스트에서 최대값을 찾으려면 다음과 같이 스트림을 사용할 수 있다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int max = numbers.stream().max(Integer::compare).get();

java.util.stream.Stream 인터페이스에는 filter, map, reduce, collect 등 다양한 배열형 데이터 처리 메서드가 선언되어 있다. 이 메서드들을 조합하면 다양한 작업을 수행할 수 있다.

람다와 스트림을 사용할 경우 장점은 이러한 코딩 표현상의 간결함, 효율성도 있지만 대량의 데이터를 처리할 경우 내부적으로 병렬 처리를 지원하기 때문에 성능도 향상되는 면도 있다.

자, 그럼 이런 좋은 도구를 사용할 때 어떤 주의할 점이 있을 수 있는지 알아보기로 하자.

람다, 스트림을 사용할 때의 주의할 점

문제가 될 수 있는 대부분은 함수형 프로그래밍 원칙에 맞지 않는 방법을 사용하는 경우다. 원칙적으로 함수형 프로그래밍에 맞는 방법으로 바꾸는 것이 맞고 익숙해진다면 당연하게 사용하게 될 것이다.

참고로 함수형 프로그래밍이라면 람다 표현식은 순수 함수(pure function)여야 한다. 즉, 람다 표현식은 내부에서 외부 변수를 변경하거나(side effect) 외부 변수를 참조해서는 안 된다.

맺음말

자바 람다, 스트림이 없어도 다른 방법으로 코딩하면 된다고 생각하는 분들이 아직 계신가? 아니면 이제 문제점도 알았겠다 람다, 스트림은 식은 죽 먹기라고 생각하는 분들이 더 많을까.

함수형 프로그래밍은 이미 자바스크립트나 다른 언어에서도 일반화된 개념으로 코딩 효율성이나 가독성, 성능 등에서 장점이 확실한 스타일이다. 새롭다고 어려운 게 아니다. 어렵다고 생각하니 계속 놔둬서 새로워 보이는 것이다.