1. 함수형 인터페이스 사용 (Java 8+)
기본적인 커링 구현
import java.util.function.Function;
public class CurryingExample {
// 3개 인자를 받는 함수를 커링
public static Function<Integer, Function<Integer, Function<Integer, Integer>>>
curriedAdd() {
return a -> b -> c -> a + b + c;
}
public static void main(String[] args) {
// 사용 예
Function<Integer, Function<Integer, Function<Integer, Integer>>> add = curriedAdd();
int result1 = add.apply(1).apply(2).apply(3); // 6
System.out.println(result1);
// 부분 적용
Function<Integer, Function<Integer, Integer>> add5 = add.apply(5);
Function<Integer, Integer> add5And3 = add5.apply(3);
int result2 = add5And3.apply(2); // 10
System.out.println(result2);
}
}
더 복잡한 예제 - 문자열 처리
import java.util.function.Function;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StringCurrying {
// 문자열 포함 검사 함수
public static Function<String, Function<String, Boolean>> contains() {
return substring -> str -> str.contains(substring);
}
// 문자열 변환 함수
public static Function<String, Function<String, String>> replace() {
return target -> replacement -> str -> str.replace(target, replacement);
}
public static void main(String[] args) {
List<String> frameworks = Arrays.asList("React", "Angular", "Vue", "Svelte");
// 부분 적용된 함수 생성
Function<String, Boolean> hasReact = contains().apply("React");
// 스트림과 함께 사용
List<String> reactItems = frameworks.stream()
.filter(hasReact::apply)
.collect(Collectors.toList());
System.out.println(reactItems); // [React]
// 문자열 치환
Function<String, String> replaceJavaWithKotlin =
replace().apply("Java").apply("Kotlin");
String result = replaceJavaWithKotlin.apply("I love Java programming");
System.out.println(result); // I love Kotlin programming
}
}
2. 제네릭을 활용한 범용 커링 유틸리티
import java.util.function.*;
public class CurryUtil {
// 2개 인자 함수 커링
public static <A, B, R> Function<A, Function<B, R>>
curry(BiFunction<A, B, R> function) {
return a -> b -> function.apply(a, b);
}
// 3개 인자 함수를 위한 함수형 인터페이스
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
// 3개 인자 함수 커링
public static <A, B, C, R> Function<A, Function<B, Function<C, R>>>
curry(TriFunction<A, B, C, R> function) {
return a -> b -> c -> function.apply(a, b, c);
}
public static void main(String[] args) {
// BiFunction 커링
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
Function<Integer, Function<Integer, Integer>> curriedMultiply = curry(multiply);
Function<Integer, Integer> double_ = curriedMultiply.apply(2);
System.out.println(double_.apply(5)); // 10
// TriFunction 커링
TriFunction<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedAdd3 = curry(add3);
int result = curriedAdd3.apply(1).apply(2).apply(3);
System.out.println(result); // 6
}
}
3. 클래스 기반 구현
public class MathOperations {
public static class CurriedAdd {
private final int first;
private CurriedAdd(int first) {
this.first = first;
}
public static CurriedAdd of(int first) {
return new CurriedAdd(first);
}
public CurriedAddSecond with(int second) {
return new CurriedAddSecond(first, second);
}
public class CurriedAddSecond {
private final int second;
private CurriedAddSecond(int first, int second) {
this.second = second;
}
public int and(int third) {
return first + second + third;
}
}
}
public static void main(String[] args) {
int result = CurriedAdd.of(1).with(2).and(3); // 6
System.out.println(result);
// 부분 적용
CurriedAdd add5 = CurriedAdd.of(5);
CurriedAdd.CurriedAddSecond add5And3 = add5.with(3);
int result2 = add5And3.and(2); // 10
System.out.println(result2);
}
}
4. 실용적인 예제 - HTTP 클라이언트
import java.util.function.Function;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
public class CurriedHttpClient {
public static Function<String, Function<String, Function<String, HttpRequest>>>
httpRequest() {
return method -> url -> body -> {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url));
switch (method.toUpperCase()) {
case "GET":
builder.GET();
break;
case "POST":
builder.POST(HttpRequest.BodyPublishers.ofString(body));
break;
case "PUT":
builder.PUT(HttpRequest.BodyPublishers.ofString(body));
break;
}
return builder.build();
};
}
public static void main(String[] args) {
// 부분 적용된 함수들 생성
Function<String, Function<String, HttpRequest>> get =
httpRequest().apply("GET");
Function<String, Function<String, HttpRequest>> post =
httpRequest().apply("POST");
// 사용
HttpRequest getRequest = get.apply("https://api.example.com/users").apply("");
HttpRequest postRequest = post.apply("https://api.example.com/users")
.apply("{\"name\":\"김철수\"}");
System.out.println("GET: " + getRequest.uri());
System.out.println("POST: " + postRequest.uri());
}
}
5. Vavr 라이브러리 사용
Vavr(이전 Javaslang)는 자바에서 함수형 프로그래밍을 더 쉽게 할 수 있게 해주는 라이브러리입니다:
// Maven 의존성 필요: io.vavr:vavr:0.10.4
import io.vavr.Function2;
import io.vavr.Function3;
public class VavrCurrying {
public static void main(String[] args) {
// Function2 커링
Function2<Integer, Integer, Integer> add = (a, b) -> a + b;
Function1<Integer, Function1<Integer, Integer>> curriedAdd = add.curried();
int result1 = curriedAdd.apply(5).apply(3); // 8
// Function3 커링
Function3<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
Function1<Integer, Function1<Integer, Function1<Integer, Integer>>> curriedAdd3 =
add3.curried();
int result2 = curriedAdd3.apply(1).apply(2).apply(3); // 6
System.out.println(result1); // 8
System.out.println(result2); // 6
}
}
자바 커링의 한계와 특징
장점:
- Java 8+ 람다와 함수형 인터페이스로 구현 가능
- 타입 안정성 보장
- 부분 적용을 통한 코드 재사용성 향상
단점:
- JavaScript에 비해 문법이 복잡하고 장황함
- 제네릭 타입 선언이 복잡해질 수 있음
- 함수 체이닝이 길어지면 가독성이 떨어질 수 있음
- 성능 오버헤드 (객체 생성, 메서드 호출 증가)
자바에서 커링은 가능하지만, 언어의 특성상 JavaScript만큼 자연스럽지는 않습니다. 하지만 함수형 프로그래밍 패러다임을 적용하고 코드의 재사용성을 높이는 데는 유용한 기법입니다.