자바는 8 버전부터 람다(Lambda
)와 스트림(Stream
)을 도입하며 기존의 객체 지향 프로그래밍 언어로서의 한계를 극복하고 함수형 프로그래밍 패러다임을 지원하기 시작하였다.
왜! 람다와 스트림을 도입한 건지, 이들이 함수형 프로그래밍과 어떤 연관이 있는지 araboza
기존에는 어땠을까?
자바 7까지는 반복적인 데이터 처리를 위해 명령형 프로그래밍(Imperative Programming)을 주로 사용했다.
이 방식은 코드가 길고 복잡해지는 문제가 있었으며 간단한 작업조차도 반복문과 조건문을 결합해야 하는 번거로움이 있었다.
예를 들어 리스트에서 특정 조건을 만족하는 데이터를 필터링하려면 다음과 같은 절차적 접근 방식을 사용해야 했다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name);
}
}
System.out.println(result); // [Alice]
위 코드는 필터링 작업을 위해 반복문과 조건문을 사용하여 결과를 저장하기 위해 별도의 리스트를 생성하는 등 비교적 많은 코드가 요구된다.
함수형 프로그래밍이란?
함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결하는 기법이다.
현대 프로그래밍에서는 AI, 빅데이터 등 데이터가 계속 방대해지고 있기 때문에 이 데이터를 빠르고 안전하게 병렬적으로 처리하는 게 중요해졌다.
함수형 프로그래밍은 데이터의 상태 변경을 최소화하고 부작용을 없애는 방식으로 안전한 병렬 처리가 가능하도록 설계되었다.
함수형 프로그래밍의 특징
함수형 프로그래밍의 특징은 크게 4가지로 요약할 수 있다.
특징 | 설명 |
---|---|
순수 함수 | 동일한 입력값에 대해 항상 동일한 결과를 반환하며, 외부 상태를 변경하지 않는다. |
고차 함수 | 함수를 인자로 전달하거나 반환값으로 사용할 수 있다. |
불변성 | 데이터는 변경되지 않으며, 데이터를 수정하면 새 데이터를 생성한다. |
선언형 프로그래밍 | "어떻게(How)"가 아니라 "무엇(What)"을 중심으로 코드를 작성한다. |
순수 함수
순수 함수가 뭔지 설명하기 전에 먼저 부수효과를 알아야 한다.
부수효과(Side Effect)란?
함수가 실행되는 동안 함수의 외부 상태를 변경하거나 외부 상태에 의존하는 것을 의미한다.
부수효과는 코드의 예측 가능성을 낮추고 디버깅을 어렵게 만드는 원인이 된다.
쉽게 말해, 함수가 "순수"하지 않은 행동을 하는 것이 부수효과라고 할 수 있다. 대표적인 부수효과에는 다음과 같은 것들이 있다
- 전역 변수나 함수 외부의 변수를 변경함
- 입력값(인자)의 상태를 변경함
- 파일, 데이터베이스, 네트워크와 같은 외부 시스템과 상호작용
- 화면에 값을 출력하거나 사용자 입력을 받는 등의 I/O 작업
순수 함수란 이 부수 효과들을 제거한 함수를 말한다.
즉, 같은 입력값에 대해 항상 동일한 결과를 반환하며, 함수 실행 중 외부 상태를 변경하지 않는 것을 말한다. (다른데선 결정론적 함수라고도 한다.)
고차 함수
함수형 프로그래밍은 함수를 일급 객체로 다룬다.
함수를 변수에 저장하거나 다른 함수의 인자로 전달할 수 있게 함수를 파라미터화
한 것을 말한다.
불변성
불변성은 데이터의 상태를 변경하지 않고, 변경이 필요한 경우 새로운 데이터를 생성하여 반환하는 특성을 말한다.
때문에 병렬 처리에서 동시성 문제를 방지하는 데 유용한 특징이다!
선언형 프로그래밍
기존에 명령형 프로그래밍은 컬렉션을 다룰 때 “무엇을”하려는지 보다 “어떻게”하는지에 집중했었다.
선언형 프로그래밍에서는 "프로세스를 직접 기술하지 않고 결과를 기술"한다. 즉, 작업의 수행 방식을 구체적으로 명시하지 않고, 원하는 결과를 기술하여 실행 환경이 해당 결과를 도출하도록 맡긴다.
예제를 통한 비교
다음은 컬렉션에서 짝수만 추출하는 코드를 작성해 명령형 프로그래밍과 함수형 프로그래밍을 비교해 보자.
명령형 프로그래밍
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
evens.add(number);
}
}
System.out.println(evens); // [2, 4, 6]
이 방식은 짝수를 추출하기 위해 반복문과 조건문을 사용하여 "어떻게" 수행할지를 명시한다.
함수형 프로그래밍
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6]
이 방식은 "짝수를 필터링하여 리스트를 생성하라"는 의도만 표현하며, 세부적인 수행 방식은 스트림 API에 맡긴다.
collect함수의 파라미터로 Collectors.toList()
가 전달된 것이 보이는가!
참고
Lambda & Stream의 도입 배경과 아름다운 동작 원리, 최적화 전략까지!
함수형 프로그래밍(Functional Programming) 이란?
'💻 Dev > Java' 카테고리의 다른 글
JDBC 드라이버 로딩으로 알아보는 Class.forName (0) | 2025.01.03 |
---|---|
스레드 로컬(Thread Local)이란? (0) | 2024.12.29 |
자바 직렬화(Java Serializable) (0) | 2024.12.27 |
리플렉션(Reflection)으로 DI 구현해보기 +단점 (0) | 2024.12.22 |
Stack은 왜 상속의 실패 사례일까? (0) | 2024.12.12 |
System.out.println을 실무에서 사용하면 안되는 이유 (1) | 2024.12.05 |