[Java] 람다식과 함수형 인터페이스
Table of Contents
Lambda 식#
JDK1.8 부터 람다식이 추가되었다. 람다식은 메서드를 하나의 식(expression)으로 표현한 것이다. 메서드를 람다식으로 표현하면, 메서드의 이름과 반환값이 사라진다. 그래서 람다식을 익명 함수(annonymous function) 이라고도 한다.
// 더하는 메서드
int sum(int a, int b){
return a+b;
}
// 람다식으로 표현
(int a, int b) -> {a+b}
함수형 인터페이스(Functional Interface)#
자바에서 모든 메서드는 클래스 내에 포함되어야 한다. 그렇다면 람다식은 어떤 클래스에 포함되어 있을까? 사실 람다식이 메서드와 동등한 것이 아니라, 익명 클래스의 객체와 동일하다.
(int a, int b) -> a > b ? a : b
// 위와 동등
new Object(){
int max(int a, int b){
return a > b ? a : b
}
}
그렇다면 람다식으로 정의된 익명 객체의 메서드를 어떻게 호출할 수 있을까? 참조변수가 있어야 객체의 메서드를 호출할 수 있으므로, 이 익명 객체의 주소를 f 라는 참조변수에 저장하자.
타입 f = (int a, int b) -> a > b ? a : b;
f 는 참조변수이므로, 타입에는 클래스 또는 인터페이스가 가능하다. 그리고 람다식과 동등한 메서드가 정의되어 있어야 한다. 그래야 참조변수로 익명 객체의 메서드를 호출할 수 있다.
interface MyFunction(){
public abstract int max(int a, int b);
}
MyFunction f = new MyFunction() {
public int max(int a, int b){
return a > b ? a : b;
}
}
MyFunction f = (int a, int b) -> a > b ? a : b; // 익명 객체 MyFunction 을 람다식으로 대체
int big = f.max(5,3); // 익명 객체의 메서드 호출
이렇게 하나의 추상 메서드(abstract method) 만 선언된 인터페이스를 함수형 인터페이스(Functional Interface) 이라고 한다.
@FunctionalInterface
interface MyFunction{
public abstract int max(int a, int b);
}
함수형 인터페이스는 단 하나의 추상 메서드만 정의되어 있어야 한다. 그래야 람다식과 객체의 메서드가 1:1 로 연결되기 때문이다.
java.util.function 패키지#
java.util.function 패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 두었다.
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
java.lang.Runnable | void run() | 매개변수X, 반환값X |
Supplier<T> | T get() -> T | 매개변수X, 반환값 O |
Consumer<T> | void accept(T t) | 매개변수O, 반환값 X |
Funtion<T,R> | R apply(T t) -> R | 일반적인 함수처럼 매개변수를 받아 결과를 반환 |
Predicate<T> | boolean test(T t) | 조건식을 표현하는데 사용. 매개변수는 하나, 반환 타입은 boolean |
자세히 코드로 한번 더 살펴보자.
Supplier#
Supplier<T>
- Supplier 는 어떠한 값을 반환한다.
- Supplier 는 필요할 때만 실행되기 때문에, produce 하기에는 비용이 들지만 몇몇 상황에서만 필요할 때 Supplier 로 전달되어 그때 실행된다. 따라서 필요 시점에만 해당 값이나 객체를 생성할 수 있어서, 불필요한 자원 사용을 줄일 수 있다. -> Lazy Evaluation
Supplier 는 오직 하나의 method get()
만 있다.
public class Company{
void createAccount(Employee e, Supplier<String> errorMsg){
if (e.getEmail() == null){
System.out.println(errorMsg.get());
}
// create Account ..
}
void createAccount(){
createAccount(new Employee, () -> "Invalid employee at" + LocalDateTime.now());
// Counld be expensive
// Or just something you want to do something later - Like get the time when the error really happens
// ...both benefits of deferred execution
}
}
어떠한 예외를 처리할 때도 Supplier 를 사용할 수 있다. 예를 들어, 입력을 받고 그 입력이 유효한 입력이 아닐 때 발생하는 예외를 잡고 재입력을 받게 하고 싶다고 하자. 아래 메서드는 매개변수로 Supplier<T>
를 받는다.
private static <T> T executeWithExceptionHandle(final Supplier<T> supplier) {
while (true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
실제 예외가 발생하는 입력을 받는 메서드에서는 아래와 같이 표현할 수 있다. Date.of(inputDate) 를 리턴하는 과정에서 여러 가지 비즈니스 로직 관련된 예외가 throw 될 수 있다.
private Date getDate() {
return executeWithExceptionHandle(() -> {
int inputDate = InputView.readDate();
return Date.of(inputDate);
});
}
getDate()
안에서는 내부적으로 람다식을 사용한다. Supplier 는 매개변수가 필요없고, 반환값이 있다고 했다. 여기서 반환값은 에러가 발생했을 시 IllegalArgumentException 이 된다.
executeWithExceptionHandle
에서는, supplier.get()
이 더 이상 IllegalArgumentException 을 반환하지 않을 때까지 실행된다.
Function#
Function<T,R>
- Function<T,R> : T = type of input, R = type of result
- Function<String, Integer> : maps String to Integer
public class Company{
Employee generateEmployeeId(Person p, Function<Person, String> mapToId){
Employee e = new Employee();
e.setEmail(p.getEmail());
e.setEmployeeId(mapToId.apply(p)); // apply() 에서 아래 선언한 실제 Function 코드 실행
}
private void AppleGenerateEmployee(){
generageEmployeeId(p, pp -> pp.gerEmail().toUpperCase()); // 코드 선언
}
private void SamsungGenerateEmployee(){
generageEmployeeId(p, pp -> pp.gerEmail() + pp.getName());
}
}
Consumer#
Consumer<T>
- 매개변수를 받고, 그것을 가지고 어떤 동작을 수행한다.
- 아무것도 반환하지 않는다.
public class Company{
// 위에 이어서..
void logEmployee(Employee e, Consumer<Employee> logger){
System.out.println("Printing Employee log");
logger.accept(e);
}
// call the method
void logInDetail(){
logEmployee(new Employee(), e -> {
System.out.println(e.getEmail());
System.out.println(e.getEmployeeId());
})
}
void logLight(){
logEmployee(new Employee(), e -> System.out.println(e.getEmail()));
}
}
Reference#
- 자바의 정석 7판, ch.14
- https://www.youtube.com/watch?v=tj5sLSFjVj4