Java - 람다식(Lambda)과 함수형 인터페이스

람다식(Lambda expression)은 Java 8에 도입되었으며, 함수형 인터페이스 객체를 표현합니다. 즉, 람다식은 추상 메소드(인터페이스)를 구현하는데, 이름이 없기 때문에 익명 함수(Anonymous Function)와 비슷합니다.

람다식은 익명 함수(클래스, 객체)를 사용하는 것보다 더 적은 코드로 동일한 내용을 구현할 수 있습니다. 함수형 프로그래밍을 할 때 람다식으로 코드를 간결하게 하고 가독성을 향상시킬 수 있습니다.

익명 함수에 대한 자세한 내용은 Java - 익명 클래스(Anonymous class)를 참고해주세요.

1. 람다식과 익명 함수 차이점

먼저 람다식과 익명 함수를 사용하지 않을 때는 아래와 같이 클래스에 대한 객체를 직접 생성하고 함수에 인자로 전달해야 합니다. Math 인터페이스를 구현한 MyMath 클래스의 객체가 한번만 사용되고 재사용되지 않는다면, 이런 방식은 비효율적일 수 있습니다. 이런 경우 익명 함수를 사용하면 클래스를 매번 구현하지 않아도 되서 적은 코드로 동일한 내용을 구현할 수 있습니다.

public class Example {

    interface Math {
        int sum(int n1, int n2);
    }

    static class MyMath implements Math {
        public int sum(int n1, int n2) {
            return n1 + n2;
        }
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {
        Math math = new MyMath();
        int result = doSomething(math);
        System.out.println(result);  // 30
    }
}

아래 예제는 익명 함수를 사용하여 동일한 내용을 구현한 예제입니다. 한번만 사용되는 객체를 만들기 위해 클래스를 구현하지 않아도 됩니다. 그렇기 때문에 익명 객체는 클래스 이름이 없습니다.

public class Example1 {

    interface Math {
        abstract int sum(int n1, int n2);
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething(new Math() {  // Anonymous function
            @Override
            public int sum(int n1, int n2) {
                return n1 + n2;
            }
        });
        System.out.println(result);  // 30
    }
}

아래 예제는 람다식을 사용하여 동일 내용을 구현한 예제입니다. 익명 함수와 매우 비슷하지만 불필요한 선언부들이 모두 생략되어있고 인터페이스의 함수의 구현부만 정의하고 있습니다. 코드가 가장 간결합니다.

public class Example2 {

    interface Math {
        int sum(int n1, int n2);
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {

        int result = doSomething((n1, n2) -> n1 + n2);  // Lambda
        System.out.println(result);  // 30
    }
}

2. 람다식의 Syntax

일반적으로 람다식은 () -> expression처럼, 한줄로 작성합니다. 람다식이 어떤 값을 리턴해야 한다면 표현식(expression)의 수행 결과가 리턴됩니다.

(parameter) -> expression

인자가 많을 때는 아래와 같이 인자를 추가할 수 있습니다.

(parameter1, parameter2) -> expression

표현식을 한 줄로 구현이 어려울 때는 { }를 사용하여 여러 줄로 구현할 수 있습니다. 대신 명시적으로 return을 입력해줘야 합니다.

(parameter1, parameter2) -> { code block }

위의 람다식 예제를 괄호를 사용하여 여러 줄로 구현하면 아래와 같이 구현할 수 있습니다.

int result = doSomething((n1, n2) -> {
    int res1 = n1 * 10;
    int res2 = n2 * 10;
    return res1 + res2;
});

3. 람다식의 특징

위에서 일반 클래스, 익명 클래스, 람다식으로 구현한 예제를 살펴보았는데요. 람다식의 특징들을 정리해보면 다음과 같습니다.

  • 클래스를 구현하지 않아도 된다.
  • 인터페이스(추상 메소드)를 구현한다.
  • Abstract 클래스의 경우, 익명 함수는 가능하지만 람다식은 구현 안됨.
  • 인자를 전달할 수 있고, 결과를 리턴할 수 있다.
  • 코드가 간결하다.

4. 람다식과 함수형 인터페이스(Functional Interface)

람다식은 위의 예제처럼 직접 정의한 인터페이스에 대해서 사용할 수 있지만, 이미 정의된 함수형 인터페이스에도 사용할 수 있습니다.

함수형 인터페이스는 아래와 같은 함수들을 제공하고 있습니다. 따라서, 직접 정의하지 않고 이런 함수들을 이용하면 됩니다.

  • Runnable : 인자를 받지 않고 리턴 값 없음
  • Supplier : 인자를 받지 않고 T 타입 객체 리턴
  • Consumer : T 타입 객체를 인자로 받고 리턴 값 없음
  • Function<T, R> : T 타입 객체를 인자로 받고, R 타입 객체 리턴
  • Predicate : T타입 객 인자로 받고, boolean 리턴

함수형 인터페이스에 대한 자세한 내용은 Java8 - 함수형 인터페이스(Functional Interface) 이해하기를 참고해주세요.

Supplier<T>를 예로 들면, 아래 코드에서 인자를 받지 않고 String을 리턴하는 Supplier의 람다식을 구현하였습니다. get() 호출 시, 람다식에서 정의된 구현이 동작하면서 String이 리턴됩니다.

Supplier<String> getString = () -> "Happy new year!";
System.out.println(getString.get());  // Happy new year!

다른 예로, 위에서 구현한 sum() 예제를 함수형 인터페이스와 람다식을 사용하여 다시 구현해보겠습니다.

BiFunction<T, U, R>는 T와 U 타입의 인자를 전달하고 R 타입의 객체를 리턴하는 함수형 인터페이스입니다. 기존에 직접 정의한 Math 인터페이스는 BiFunction로 대체할 수 있기 때문에 삭제해도 됩니다. apply()를 호출할 때 전달된 인자와 함께 람다식이 수행되고 결과가 리턴됩니다.

import java.util.function.BiFunction;

public class Example2 {

    static int doSomething(BiFunction<Integer, Integer, Integer> sum) {
        return sum.apply(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething((n1, n2) -> n1 + n2);  // Lambda
        System.out.println(result);  // 30
    }
}

만약 익명 함수를 사용하여 구현한다면, 아래와 같이 구현할 수 있습니다. 하지만 함수형 인터페이스를 많이 사용하게 될 때, 익명 함수를 사용한다면 코드가 길어지고 가독성이 떨어질 수 있습니다.

import java.util.function.BiFunction;

public class Example2 {

    static int doSomething(BiFunction<Integer, Integer, Integer> sum) {
        return sum.apply(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething(new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer n1, Integer n2) {
                return n1 + n2;
            }
        });  // Anonymous function
        System.out.println(result);  // 30
    }
}
Loading script...
codechachaCopyright ©2019 codechacha