함수형 프로그래밍은 "데이터의 변환" 에 집중하여 더 안정적이고 유지보수하기 쉬운 코드를 만든다.
실무에서 재귀가 유용한 경우
acc
)을 인자로 전달 → 스택 프레임 재사용 가능// 일반 재귀 (스택 오버플로우 위험)
static long factorial(long n) {
return n == 1 ? 1 : n * factorial(n - 1);
}
// 꼬리 재귀 (Java는 최적화 미지원, but 패턴 적용 가능)
static long factorialTail(long n, long acc) {
return n == 1 ? acc : factorialTail(n - 1, acc * n);
}
자바 8의 함수형 프로그래밍은 코드의 안정성과 재사용성을 높이는 다양한 기법을 제공
불변성과 함수 조합을 통해 다음과 같은 이점
- 동시성 문제 최소화
- 코드 가독성 향상
- 버그 발생 가능성 감소
- 테스트 용이성 증가
Function<String, Integer> strToInt = Integer::parseInt; // 메서드 참조 활용
Function<A, C> compose(Function<B, C> g, Function<A, B> f) {
return x -> g.apply(f.apply(x)); // 함수 조합
}
List<Integer> newList = new ArrayList<>(originalList); // 방어적 복사
IntStream.range(1, 10).filter(n -> n%2 == 0); // 중간 연산만 정의
Supplier<Double> rand = Math::random; // 호출 시마다 값 생성
static String patternMatch(Object obj) {
if (obj instanceof String s) return "String: " + s;
if (obj instanceof Integer i) return "Integer: " + i;
return "Unknown"; // 자바 16 패턴 매칭
}
// 변환 요소와 기준치만 고정해두고 변환할 값만 입력받는 함수
DoubleUnaryOperator convertCtoF = curriedConverter(9.0/5, 32); // 섭씨→화씨 변환
Map<String, Function<Double, Double>> cache = new HashMap<>(); // 계산 결과 저장
프로그래밍 언어는 점점 더 "함수형"으로 진화하고 있으며, Java도 8부터 도입된 람다, 스트림, Optional 등으로 함수형 프로그래밍(FP)의 장점을 받아들이기 위한 변화를 꾀했다.
하지만 함수형 프로그래밍이 언어에 깊이 뿌리내리려면 문법 자체가 간결하고 불변성, 일급 함수, 고차 함수 등의 개념이 자연스럽게 녹아 있어야한다.
FP를 자연스럽게 지원하는 언어, 스칼라(Scala) 를 통해 자바와 비교하며 OOP와 FP의 조화를 살펴본다.
간단한 예:
// 1. 함수를 변수에 할당
val greet: String => String = (name: String) => s"Hello, $name!"
// 2. 함수를 인자로 전달
def printFormatted(formatFunc: String => String, name: String): Unit = {
println(formatFunc(name))
}
// 3. 함수를 반환값으로 사용
def createGreeter(prefix: String): String => String = {
(name: String) => s"$prefix $name!"
}
s"Hello ${n} bottles"
"Hello " + n + " bottles"
스칼라:
val list = List(1, 2, 3) // 스칼라의 `List`, `Set`, `Map`은 기본적으로 불변
자바:
List<Integer> list = Arrays.asList(1, 2, 3); // 변경 가능한 경우 많음
val
: 자바의 final
처럼 재할당 불가var
: 가변 변수 (지양)val name = "Alice" // 불변
var count = 10 // 가변
val book = (2018, "Modern Java in Action", "Manning")
// 자바에서는 클래스를 별도로 만들어야 하는 작업이다.
Option
은 값의 존재/부재를 안전하게 표현val maybeName: Option[String] = Some("Alice") // 또는 None
// 스칼라는 함수 자체를 변수로 다룰 수 있다.
val add = (x: Int, y: Int) => x + y
// 자바도 람다로 비슷하게 할 수 있지만, 함수 자체를 값처럼 다루는 것이 아니라
// Function<T, R> 같은 인터페이스 구현이 기반
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
// 스칼라는 람다(익명 함수)에서 외부의 변수를 자유롭게 참조할 수 있으며,
// 이때 람다가 외부 환경을 "포착"하여 클로저가 된다.
val add = (x: Int) => x + externalVar
final
또는 사실상 final이어야 합니다.// # scala
var externalVar = 10
val add = (x: Int) => x + externalVar // 클로저 생성 시점의 externalVar 캡처
println(add(5)) // 15 (10 + 5)
externalVar = 20 // 외부 변수 값 변경
println(add(5)) // 25 (20 + 5) → 캡처된 변수의 최신 값 반영
// # java
int external = 10;
Function<Integer, Integer> add = x -> x + external;
external = 20; // Error: 외부 변수 수정 불가
스칼라
def add(x: Int)(y: Int): Int = x + y
val add5 = add(5)_ // 새로운 함수 생성
자바: 커링을 직접 구현하려면 복잡한 람다 체인이 필요
// 생성자와 불변 필드를 동시에 선언
class Book(val title: String, val year: Int)
trait
는 자바의 interface
보다 강력하다.trait A { def hello() = println("Hello from A") }
trait B { def hello() = println("Hello from B") }
class C extends A with B // 트레이트 다중 상속
요약
스칼라는 자바보다 간결하고 함수형 스타일을 자연스럽게 지원하며, 불변 컬렉션, 튜플, 일급 함수, 트레이트 등
다양한 기능을 제공해 객체지향과 함수형 프로그래밍의 조화를 이룬 언어다.
list.sort((a, b) -> a.compareTo(b));
list.sort(String::compareTo);
List<String> result = list.stream()
.filter(s -> s.startsWith("J"))
.map(String::toUpperCase)
.collect(Collectors.toList());
filter
, map
, collect
등의 메서드 체이닝으로 가독성 ↑.parallelStream()
으로 간단하게Optional<String> name = Optional.ofNullable(user.getName());
name.ifPresent(System.out::println);
ifPresent
, orElse
, map
, filter
등으로 안전하고 깔끔하게 처리 가능.CompletableFuture.supplyAsync(() -> getData())
.thenApply(data -> process(data))
.thenAccept(result -> display(result));
thenCompose
, thenCombine
등으로 여러 작업을 유연하게 조합 가능.Flow
인터페이스가 도입됨.interface MyInterface {
default void hello() {
System.out.println("Hello from interface!");
}
}
module-info.java
를 사용해 코드 구조를 명확히 나누고, 의존성을 명시할 수 있게 됨.var list = new ArrayList<String>();
List.of()
, Set.of()
→ 불변 컬렉션 생성if
나 instanceof
를 더 직관적으로 사용할 수 있게 하는 문법이 추가되고 있다.if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
자바는 람다, 스트림, Optional, CompletableFuture 등으로 간결하고 안전하며,
모듈화, 타입 추론 등으로 더 유연해졌고,
앞으로도 패턴 매칭, 값 타입, 제네릭 강화 등 혁신은 계속되고 있다.