// 일반 함수 - 이름이 있음
public int add(int x) {
return x + 1;
}
// 람다 - 이름이 없음
(int x) -> {return x + 1;}
여러 추상 메서드
package lambda.lambda1;
public interface NotSamInterface {
void run();
void go();
}
단일 추상 메서드
package lambda.lambda1;
public interface SamInterface {
void run();
}
package lambda.lambda1;
public class SamMain {
public static void main(String[] args) {
SamInterface samInterface = () -> {
System.out.println("sam");
};
samInterface.run();
// 컴파일 오류
/*
NotSamInterface notSamInterface = () -> {
System.out.println("not sam");
};
notSamInterface.run(); // ?
notSamInterface.go(); // ?
*/
}
}
=>
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
}
public class ElectricCar extends Car {
@Override
public void movee() {
System.out.println("전기차를 빠르게 이동합니다.");
}
}
메서드를 재정의할 때 실수로 재정의할 메서드 이름을 다르게 적으면 재정의가 되지 않는다. 이 예제에서 부모는
move() 인데 자식은 movee() 라고 e 를 하나 더 잘못 적었다. 이런 문제를 컴파일 단계에서 원천적으로 막기 위해
@Override 애노테이션을 사용한다. 이 애노테이션 덕분에 개발자가 할 수 있는 실수를 컴파일 단계에서 막을 수 있
고, 또 개발자는 이 메서드가 재정의 메서드인지 명확하게 인지할 수 있다.
람다를 함수형 인터페이스에 할당할 때는 메서드의 형태를 정의하는 요소인 메서드 시그니처가 일치해야 한다.
메서드 시그니처의 주요 구성 요소는 다음과 같다.
1. 메서드 이름
2. 매개변수의 수와 타입(순서 포함)
3. 반환 타입
MyFunction 예시
예를 들어 MyFunction 의 apply 메서드를 살펴보자.
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b);
}
이 메서드의 시그니처
MyFunction myFunction = (int a, int b) -> {
return a + b;
};
람다는 익명 함수이므로 시그니처에서 이름은 제외하고, 매개변수, 반환 타입이 함수형 인터페이스에 선언한 메서드와 맞아야 한다.
이 람다는 매개변수로 int a , int b , 그리고 반환 값으로 a + b 인 int 타입을 반환하므로 시그니처가 맞다. 따라서 람다를 함수형 인터페이스에 할당할 수 있다.
참고로 람다의 매개변수 이름은 함수형 인터페이스에 있는 메서드 매개변수의 이름과 상관없이 자유롭게 작성해도 된다. 타입과 순서만 맞으면 된다.
매개변수 타입: 생략 가능하지만 필요하다면 명시적으로 작성할 수 있다.
반환 타입: 문법적으로 명시할 수 없고, 식의 결과를 보고 컴파일러가 항상 추론한다.
람다는 보통 간략하게 사용하는 것을 권장한다.
- 단일 표현식이면 중괄호와 리턴을 생략하자.
- 타입 추론을 통해 매개변수의 타입을 생략하자. (컴파일러가 추론할 수 있다면, 생략하자)
람다를 변수에 대입하기
package lambda.lambda2;
import lambda.MyFunction;
// 1. 람다를 변수에 대입하기
public class LambdaPassMain1 {
public static void main(String[] args) {
MyFunction add = (a, b) -> a + b;
MyFunction sub = (a, b) -> a - b;
System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));
MyFunction cal = add;
System.out.println("cal(add).apply(1, 2) = " + cal.apply(1, 2));
cal = sub;
System.out.println("cal(sub).apply(1, 2) = " + cal.apply(1, 2));
}
}
람다를 메서드(함수)에 전달하기
앞서 본 것과 같이 람다는 변수에 전달할 수 있다.
같은 원리로 람다를 매개변수를 통해 메서드(함수)에 전달할 수 있다.
package lambda.lambda2;
import lambda.MyFunction;
// 2. 람다를 메서드(함수)에 전달하기
public class LambdaPassMain2 {
public static void main(String[] args) {
MyFunction add = (a, b) -> a + b;
MyFunction sub = (a, b) -> a - b;
System.out.println("변수를 통해 전달");
calculate(add);
calculate(sub);
System.out.println("람다를 직접 전달");
calculate((a, b) -> a + b);
calculate((a, b) -> a - b);
}
static void calculate(MyFunction function) {
int a = 1;
int b = 2;
System.out.println("계산 시작");
int result = function.apply(a, b);
System.out.println("계산 결과: " + result);
}
}
람다를 반환하기
package lambda.lambda2;
import lambda.MyFunction;
// 3. 람다를 반환하기
public class LambdaPassMain3 {
public static void main(String[] args) {
MyFunction add = getOperation("add");
System.out.println("add.apply(1, 2) = " + add.apply(1, 2));
MyFunction sub = getOperation("sub");
System.out.println("sub.apply(1, 2) = " + sub.apply(1, 2));
MyFunction xxx = getOperation("xxx");
System.out.println("xxx.apply(1, 2) = " + xxx.apply(1, 2));
}
// 람다를 반환하는 메서드
static MyFunction getOperation(String operator) {
switch (operator) {
case "add":
return (a, b) -> a + b;
case "sub":
return (a, b) -> a - b;
default:
return (a, b) -> 0;
}
}
}
고차 함수(Higher-Order Function)
함수를 인자로 받는 경우
// 함수(람다)를 매개변수로 받음
static void calculate(MyFunction function) {
// ...
}
함수를 반환하는 경우
// 함수(람다)를 반환
static MyFunction getOperation(String operator) {
// ...
return (a, b) -> a + b;
}
[문제와 풀이 2, 3이 매우 중요!]
문제 설명
다음 코드는 화면에 여러 종류의 인삿말 메시지를 출력하지만, 모든 메서드마다 === 시작 === 과 === 끝 === 을 출력하는 로직이 중복되어 있다. 중복되는 코드를 제거하고, 변하는 부분(인삿말 메시지)만 매개변수로 받도록 리팩토링 해라
예시 코드
package lambda.ex1;
public class M1Before {
public static void greetMorning() {
System.out.println("=== 시작 ===");
System.out.println("Good Morning!");
System.out.println("=== 끝 ===");
}
public static void greetAfternoon() {
System.out.println("=== 시작 ===");
System.out.println("Good Afternoon!");
System.out.println("=== 끝 ===");
}
public static void greetEvening() {
System.out.println("=== 시작 ===");
System.out.println("Good Evening!");
System.out.println("=== 끝 ===");
}
public static void main(String[] args) {
greetMorning();
greetAfternoon();
greetEvening();
}
}
정답
package lambda.ex1;
public class M1After {
// 하나의 메서드로 합치고, 매개변수(문자열)만 다르게 받아 처리
public static void greet(String message) {
System.out.println("=== 시작 ===");
System.out.println(message);
System.out.println("=== 끝 ===");
}
public static void main(String[] args) {
greet("Good Morning!");
greet("Good Afternoon!");
greet("Good Evening!");
}
}
문제 설명
다음 코드는, 주어진 숫자(예: 10)를 특정 단위(예: "kg")로 출력하는 간단한 메서드를 작성한 예시이다.
숫자와 단위를 나누고 재사용 가능한 메서드를 사용하도록 코드를 수정해라.
예시 코드
public class M2Before {
public static void print1() {
System.out.println("무게: 10kg");
}
public static void print2() {
System.out.println("무게: 50kg");
}
public static void print3() {
System.out.println("무게: 200g");
}
public static void print4() {
System.out.println("무게: 40g");
}
public static void main(String[] args) {
print1();
print2();
print3();
print4();
}
}
정답
package lambda.ex1;
public class M2After {
// 숫자(무게)와 단위 모두 매개변수화
public static void print(int weight, String unit) {
System.out.println("무게: " + weight + unit);
}
public static void main(String[] args) {
print(10, "kg");
print(50, "kg");
print(200, "g");
print(40, "g");
}
}
문제 설명
1부터 N까지 더하는 로직과, 배열을 정렬하는( Arrays.sort() ) 로직을 각각 실행하고, 이 두 가지 로직 모두 "실행
에 걸린 시간을 측정"하고 싶다.
"실행 시간 측정" 로직은 변하지 않는 부분
"실행할 로직"은 바뀌는 부분(1부터 N 합 구하기 vs 배열 정렬)
이 문제는 람다를 사용하지 말고 익명 클래스를 사용해서 풀어라
문제
1. 앞서 정의한 Procedure (추상 메서드 run() ) 함수형 인터페이스를 사용해라.
2. measure(Procedure p) 메서드 안에서
실행 전 시간 기록
p.run() 실행
실행 후 시간 기록
걸린 시간 출력
3. main() 에서 익명 클래스 두 가지를 만들어 각각 실행 시간을 측정해라.
(1) 1부터 N까지 합을 구하는 로직 ( measure 메서드 호출)
(2) 배열을 정렬하는 로직 ( measure 메서드 호출)
measure 메서드는 총 2번 호출된다.
(1) 1부터 N까지 합을 구하는 로직 ( measure 메서드 호출)
int N = 100;
long sum = 0;
for (int i = 1; i <= N; i++) {
sum += i;
}
(2) 배열을 정렬하는 로직 ( measure 메서드 호출)
int[] arr = { 4, 3, 2, 1 };
System.out.println("원본 배열: " + Arrays.toString(arr));
Arrays.sort(arr);
System.out.println("배열 정렬: " + Arrays.toString(arr));
정답
예시 함수형 인터페이스
package lambda;
@FunctionalInterface
public interface Procedure {
void run();
}
package lambda.ex1;
import lambda.Procedure;
import java.util.Arrays;
public class M3MeasureTime {
// 공통: 실행 시간 측정 메서드
public static void measure(Procedure p) {
long startNs = System.nanoTime();
p.run(); // 바뀌는 로직 실행 (익명 클래스 or 람다로 전달)
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}
public static void main(String[] args) {
// 1. 익명 클래스로 1부터 N까지 합 구하기
measure(new Procedure() {
@Override
public void run() {
int N = 100;
long sum = 0;
for (int i = 1; i <= N; i++) {
sum += i;
}
System.out.println("[1부터 " + N + "까지 합] 결과: " + sum);
}
});
// 2. 익명 클래스로 배열 정렬
measure(new Procedure() {
@Override
public void run() {
int[] arr = { 4, 3, 2, 1 };
System.out.println("원본 배열: " + Arrays.toString(arr));
Arrays.sort(arr);
System.out.println("배열 정렬: " + Arrays.toString(arr));
}
});
}
}
문제 설명
이전 문제에서 익명 클래스로 작성한 부분을 람다로 변경해라.
measure() 메서드와 Procedure 인터페이스는 그대로 둔다.
main() 에서 익명 클래스를 사용하지 말고, 람다를 이용하여 더욱 간결하게 코드를 작성해라.
정답
package lambda.ex1;
import lambda.Procedure;
import java.util.Arrays;
public class M4MeasureTime {
// 공통: 실행 시간 측정 메서드
public static void measure(Procedure p) {
long startNs = System.nanoTime();
p.run(); // 바뀌는 로직 실행 (익명 클래스 or 람다로 전달)
long endNs = System.nanoTime();
System.out.println("실행 시간: " + (endNs - startNs) + "ns\n");
}
public static void main(String[] args) {
// 1. 람다로 1부터 N까지 합 구하기
measure(() -> {
int N = 100;
long sum = 0;
for (int i = 1; i <= N; i++) {
sum += i;
}
System.out.println("[1부터 " + N + "까지 합] 결과: " + sum);
});
// 2. 람다로 배열 정렬
measure(() -> {
int[] arr = { 4, 3, 2, 1 };
System.out.println("원본 배열: " + Arrays.toString(arr));
Arrays.sort(arr);
System.out.println("[배열 정렬] 결과: " + Arrays.toString(arr));
});
}
}
문제 설명
"함수를 반환"하는 방식도 연습해보자. 두 정수를 받아서 연산하는 MyFunction 인터페이스를 사용해보자.
package lambda;
@FunctionalInterface
public interface MyFunction {
int apply(int a, int b);
}
static MyFunction getOperation(String operator) 라는 정적 메서드를 만들어라.
매개변수인 operator 에 따라 다음과 같은 내용을 전달하고 반환해라.
operator 가 "add"면, (a, b) 를 받아 a + b 를 리턴하는 람다를 반환해라.
"sub"면, a - b 를 리턴하는 람다를 반환해라.
그 외의 경우는 항상 0을 리턴하는 람다를 반환해라.
main() 메서드에서 getOperation("add") , getOperation("sub") , getOperation("xxx") 를
각각 호출해서 반환된 람다를 실행해라.
예시 출력
add(1, 2) = 3
sub(1, 2) = -1
xxx(1, 2) = 0 // 그 외의 경우
정답
package lambda.ex1;
import lambda.MyFunction;
public class M5Return {
// operator에 따라 다른 람다(=함수)를 반환
public static void main(String[] args) {
MyFunction add = getOperation("add");
System.out.println("add(1, 2) = " + add.apply(1, 2));
MyFunction sub = getOperation("sub");
System.out.println("sub(1, 2) = " + sub.apply(1, 2));
MyFunction xxx = getOperation("xxx");
System.out.println("xxx(1, 2) = " + xxx.apply(1, 2));
}
public static MyFunction getOperation(String operator) {
switch (operator) {
case "add":
return (a, b) -> a + b;
case "sub":
return (a, b) -> a - b;
default:
return (a, b) -> 0; // 잘못된 연산자일 경우 0 반환
}
}
}
이번 문제들은 이후에 설명할 스트림은 물론이고, 함수형 프로그래밍의 개념을 이해하기 위해 반드시 반복해서 풀어보고, 또 이해해야 한다.
각 문제에서 요구하는핵심 사항은 "함수를 매개변수로 받거나, 함수를 반환" 하는 구조를 구현하는 것이다.
람다가 아직 익숙하지 않을 것이니 먼저 익명 클래스로 구현해보고 그 다음에 람다로 구현해보자.
참고: 고차 함수(Higher-Order Function)란?
함수를 인자로 받거나, 함수를 반환하는 함수
자바에서는 함수형 인터페이스에 익명 클래스나 람다를 담아서 주고받음으로써 고차 함수를 구현할 수 있다.
요구 사항
1. 정수 리스트가 주어졌을 때, 특정 조건에 맞는 요소들만 뽑아내는 filter 함수를 직접 만들어보자.
2. filter(List list, MyPredicate predicate) 형식의 정적 메서드를 하나 작성한다.
MyPredicate 는 함수형 인터페이스이며, boolean test(int value); 같은 메서드를 가진다.
3. main() 에서 예시로 다음과 같은 상황을 실습해보자.
리스트: [-3, -2, -1, 1, 2, 3, 5]
조건 1: 음수(negative)만 골라내기
조건 2: 짝수(even)만 골라내기
예시 실행
원본 리스트: [-3, -2, -1, 1, 2, 3, 5]
음수만: [-3, -2, -1]
짝수만: [-2, 2]
함수형 인터페이스 예시
package lambda.ex2;
@FunctionalInterface
public interface MyPredicate {
boolean test(int value);
}
기본 코드 예시
package lambda.ex2;
import java.util.ArrayList;
import java.util.List;
public class FilterExample {
// 고차 함수, 함수를 인자로 받아서 조건에 맞는 요소만 뽑아내는 filter
public static List<Integer> filter(List<Integer> list, MyPredicate
predicate) {
List<Integer> result = new ArrayList<>();
for (int val : list) {
if (predicate.test(val)) {
result.add(val);
}
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(-3, -2, -1, 1, 2, 3, 5);
System.out.println("원본 리스트: " + numbers);
// 1. 음수(negative)만 뽑아내기
// 코드 작성
// 2. 짝수(even)만 뽑아내기
// 코드 작성
}
}
정답 - 익명 클래스
package lambda.ex2;
import java.util.ArrayList;
import java.util.List;
public class FilterExampleEx1 {
// 고차 함수, 함수를 인자로 받아서 조건에 맞는 요소만 뽑아내는 filter
public static List<Integer> filter(List<Integer> list, MyPredicate
predicate) {
List<Integer> result = new ArrayList<>();
for (int val : list) {
if (predicate.test(val)) {
result.add(val);
}
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(-3, -2, -1, 1, 2, 3, 5);
System.out.println("원본 리스트: " + numbers);
// 1. 음수(negative)만 뽑아내기
List<Integer> negatives = filter(numbers, new MyPredicate() {
@Override
public boolean test(int value) {
return value < 0;
}
});
System.out.println("음수만: " + negatives);
// 2. 짝수(even)만 뽑아내기
List<Integer> evens = filter(numbers, new MyPredicate() {
@Override
public boolean test(int value) {
return value % 2 == 0;
}
});
System.out.println("짝수만: " + evens);
}
}
정답 - 람다
package lambda.ex2;
import java.util.ArrayList;
import java.util.List;
public class FilterExampleEx2 {
// 고차 함수, 함수를 인자로 받아서 조건에 맞는 요소만 뽑아내는 filter
public static List<Integer> filter(List<Integer> list, MyPredicate
predicate) {
List<Integer> result = new ArrayList<>();
for (int val : list) {
if (predicate.test(val)) {
result.add(val);
}
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(-3, -2, -1, 1, 2, 3, 5);
System.out.println("원본 리스트: " + numbers);
// 1. 음수(negative)만 뽑아내기
List<Integer> negatives = filter(numbers, value -> value < 0);
System.out.println("음수만: " + negatives);
// 2. 짝수(even)만 뽑아내기
List<Integer> evens = filter(numbers, value -> value % 2 == 0);
System.out.println("짝수만: " + evens);
}
}
요구 사항
1. 문자열 리스트를 입력받아, 각 문자열을 어떤 방식으로 변환(map, mapping)할지 결정하는 함수( map )를 만들
어보자
2. map(List list, StringFunction func) 형태로 구현한다.
StringFunction 은 함수형 인터페이스이며, String apply(String s); 같은 메서드를 가진다.
3. main() 에서 다음 변환 로직들을 테스트해보자.
변환 1: 모든 문자열을 대문자로 변경
변환 2: 문자열 앞 뒤에 를 붙여서 반환(예: "hello" → "hello***" )
예시 실행
원본 리스트: [hello, java, lambda]
대문자 변환 결과: [HELLO, JAVA, LAMBDA]
특수문자 데코 결과: [***hello***, ***java***, ***lambda***]
함수형 인터페이스
package lambda.ex2;
@FunctionalInterface
public interface StringFunction {
String apply(String s);
}
코드 예시
package lambda.ex2;
import java.util.List;
public class MapExample {
// 고차 함수, 함수를 인자로 받아, 리스트의 각 요소를 변환
public static List<String> map(List<String> list, StringFunction func) {
// 코드 작성
return null; // 제거하고 적절한 객체를 반환
}
public static void main(String[] args) {
List<String> words = List.of("hello", "java", "lambda");
System.out.println("원본 리스트: " + words);
// 1. 대문자 변환
// 코드 작성
// 2. 앞뒤에 *** 붙이기 (람다로 작성)
// 코드 작성
}
}
정답
package lambda.ex2;
import java.util.ArrayList;
import java.util.List;
public class MapExample {
// 고차 함수, 함수를 인자로 받아, 리스트의 각 요소를 변환
public static List<String> map(List<String> list, StringFunction func) {
List<String> result = new ArrayList<>();
for (String str : list) {
result.add(func.apply(str));
}
return result;
}
public static void main(String[] args) {
List<String> words = List.of("hello", "java", "lambda");
System.out.println("원본 리스트: " + words);
// 1. 대문자 변환
List<String> upperList = map(words, s -> s.toUpperCase());
System.out.println("대문자 변환 결과: " + upperList);
// 2. 앞뒤에 *** 붙이기 (람다로 작성)
List<String> decoratedList = map(words, s -> "***" +***
문제 3. reduce(또는 fold) 함수 구현하기
요구 사항
1. 정수 리스트를 받아서, 모든 값을 하나로 누적(reduce)하는 함수를 만들어보자.
2. reduce(List list, int initial, MyReducer reducer) 형태로 구현한다.
MyReducer 는 int reduce(int a, int b); 같은 메서드를 제공하는 함수형 인터페이스이다.
initial 은 누적 계산의 초깃값(예: 0 또는 1 등)을 지정한다.
3. main() 에서 다음 연산을 테스트해보자.
연산 1: 리스트 [1, 2, 3, 4] 를 모두 더하기( + )
연산 2: 리스트 [1, 2, 3, 4] 를 모두 곱하기( * )
예시 실행
리스트: [1, 2, 3, 4]
합(누적 +): 10
곱(누적 *): 24
함수형 인터페이스
package lambda.ex2;
@FunctionalInterface
public interface MyReducer {
int reduce(int a, int b);
}
package lambda.ex2;
import java.util.List;
public class ReduceExample {
// 함수를 인자로 받아, 리스트 요소를 하나로 축약(reduce)하는 고차 함수
public static int reduce(List<Integer> list, int initial, MyReducer reducer) {
// 코드 작성
return 0; // 적절한 값으로 변경
}
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4);
System.out.println("리스트: " + numbers);
// 1. 합 구하기 (초깃값 0, 덧셈 로직)
// 코드 작성
// 2. 곱 구하기 (초깃값 1, 곱셈 로직)
// 코드 작성
}
}
고차 함수: MyReducer.reduce 메서드가 "함수를 인자로 받아서" 내부 로직(합산, 곱셈 등)을 다르게 수행한
다.
곱은 초깃값을 1로 한 것에 주의하자. 어떤 수 든지 0 을 곱하면 그 결과가 0 이 된다.
정답
package lambda.ex2;
import java.util.List;
public class ReduceExample {
// 함수를 인자로 받아, 리스트 요소를 하나로 축약(reduce)하는 고차 함수
public static int reduce(List<Integer> list, int initial, MyReducer reducer) {
int result = initial;
for (int val : list) {
result = reducer.reduce(result, val);
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4);
System.out.println("리스트: " + numbers);
// 1. 합 구하기 (초깃값 0, 덧셈 로직)
int sum = reduce(numbers, 0, (a, b) -> a + b);
System.out.println("합(누적 +): " + sum);
// 2. 곱 구하기 (초깃값 1, 곱셈 로직, 람다로 작성)
int product = reduce(numbers, 1, (a, b) -> a * b);
System.out.println("곱(누적 *): " + product);
}
}
요구 사항
1. 문자열을 입력받아, 새로운 함수를 반환해주는 buildGreeter(String greeting) 라는 메서드를 작성하
자.
예) buildGreeter("Hello") "Hello" 를 사용하는 새로운 함수 반환
새로운 함수는 입력받은 문자열에 대해 "Hello"(greeting) + ", " + (입력받은 문자열) 형태로
결과를 반환
2. 함수를 반환받은 뒤에, 실제로 그 함수를 호출해 결과를 확인해보자.
함수형 인터페이스 - 이전에 작성한 코드를 사용하자.
package lambda.ex2;
@FunctionalInterface
public interface StringFunction {
String apply(String s);
}
문제 예시
package lambda.ex2;
public class BuildGreeterExample {
// 고차 함수, greeting 문자열을 받아, "새로운 함수를" 반환
public static StringFunction buildGreeter(String greeting) {
// 코드 작성
return null; // 적절한 람다 반환
}
public static void main(String[] args) {
// 코드 작성
}
}
실행 결과
Hello, Java
Hi, Lambda
정답
package lambda.ex2;
public class BuildGreeterExample {
// 고차 함수: greeting 문자열을 받아, "새로운 함수를" 반환
public static StringFunction buildGreeter(String greeting) {
// 람다로 함수 반환
return name -> greeting + ", " + name;
}
public static void main(String[] args) {
StringFunction helloGreeter = buildGreeter("Hello");
StringFunction hiGreeter = buildGreeter("Hi");
// 함수가 반환되었으므로, apply()를 호출해 실제로 사용
System.out.println(helloGreeter.apply("Java")); // Hello, Java
System.out.println(hiGreeter.apply("Lambda")); // Hi, Lambda
}
}
이번에는 람다를 전달하고 또 람다를 반환까지 하는 복잡한 문제를 풀어보자.
요구 사항
1. 문자열을 변환하는 함수 두 개( MyTransformer 타입)를 받아서, f1을 먼저 적용하고, 그 결과에 f2를 적용하는
새로운 함수를 반환하는 compose 메서드를 만들어보자. 예) f2(f1(x))
2. 예시 상황:
f1 : 대문자로 바꿈
f2 : 문자 앞 뒤에 "" 을 붙임
합성 함수( compose() )를 "hello" 에 적용하면 → "HELLO**"
함수형 인터페이스
package lambda.ex2;
@FunctionalInterface
public interface MyTransformer {
String transform(String s);
}
package lambda.ex2;
public class ComposeExample {
// 고차 함수, f1, f2라는 두 함수를 인자로 받아, "f1을 먼저, f2를 나중"에 적용하는 새 함수 반환
public static MyTransformer compose(MyTransformer f1, MyTransformer f2) {
// 코드 작성
return null; // 적절한 람다 반환
}
public static void main(String[] args) {
// f1: 대문자로 변환
MyTransformer toUpper = s -> s.toUpperCase();
// f2: 앞 뒤에 "**" 붙이기
MyTransformer addDeco = s -> "**" + s + "**";
// 합성: f1 → f2 순서로 적용하는 함수
MyTransformer composeFunc = compose(toUpper, addDeco);
// 실행
String result = composeFunc.transform("hello");
System.out.println(result); // "**HELLO**"
}
}
실행 결과
**HELLO**
이번에 만나볼 고차 함수는 함수를 인자로 받아서, 또 다른 함수를 반환하는 형태이다.
힌트
문제를 풀기 쉽지 않을 것이다. compose() 메서드 안에서 MyTransformer 를 반환해야 한다. 처음에는 익명 클래
스를 사용해보자.
정답 - 익명 클래스
package lambda.ex2;
public class ComposeExampleEx1 {
// 고차 함수, f1, f2라는 두 함수를 인자로 받아, "f1을 먼저, f2를 나중"에 적용하는 새 함수 반환
public static MyTransformer compose(MyTransformer f1, MyTransformer f2) {
return new MyTransformer() {
@Override
public String transform(String s) {
String intermediate = f1.transform(s);
return f2.transform(intermediate);
}
};
}
public static void main(String[] args) {
// f1: 대문자로 변환
MyTransformer toUpper = s -> s.toUpperCase();
// f2: 앞 뒤에 "**" 붙이기
MyTransformer addDeco = s -> "**" + s + "**";
// 합성: f1 → f2 순서로 적용하는 함수
MyTransformer composeFunc = compose(toUpper, addDeco);
// 테스트
String result = composeFunc.transform("hello");
System.out.println(result); // "**HELLO**"
}
}
정답 - 람다
package lambda.ex2;
public class ComposeExampleEx2 {
// 고차 함수, f1, f2라는 두 함수를 인자로 받아, "f1을 먼저, f2를 나중"에 적용하는 새 함수 반환
public static MyTransformer compose(MyTransformer f1, MyTransformer f2) {
return s -> {
String intermediate = f1.transform(s);
return f2.transform(intermediate);
};
}
public static void main(String[] args) {
// f1: 대문자로 변환
MyTransformer toUpper = s -> s.toUpperCase();
// f2: 앞 뒤에 "**" 붙이기
MyTransformer addDeco = s -> "**" + s + "**";
// 합성: f1 → f2 순서로 적용하는 함수
MyTransformer composeFunc = compose(toUpper, addDeco);
// 테스트
String result = composeFunc.transform("hello");
System.out.println(result); // "**HELLO**"
}
}
<정리>
지금까지 진행한 5가지 문제는 자바에서 고차 함수를 구현할 때 자주 등장하는 패턴으로 구성되어 있다.
1. filter: 조건(함수)을 인자로 받아, 리스트에서 필요한 요소만 추려내기
2. map: 변환 로직(함수)을 인자로 받아, 리스트의 각 요소를 다른 형태로 바꾸기
3. reduce: 누적 로직(함수)을 인자로 받아, 리스트의 모든 요소를 하나의 값으로 축약하기
4. 함수를 반환: 어떤 문자열/정수 등을 받아서, 그에 맞는 새로운 "함수"를 만들어 돌려주기
5. 함수 합성: 두 함수를 이어 붙여, 한 번에 변환 로직을 적용할 수 있는 새 함수를 만들기