언어(Language)/Java

[Java] 자바 람다식과 함수형 인터페이스 (Lambda Expression & Functional Interface)

잇트루 2022. 9. 25. 00:00
반응형

람다식이란? (Lambda Expression)

람다식은 함수형 프로그래밍 기법을 지원하는 자바의 문법 요소이다. 람다 계산법이 프로그래밍 언어에 도입하면서 사용되기 시작했다.

람다식은 메서드를 하나의 식(Expression)으로 표현한 것으로, 코드를 간결하면서 명확하게 표현할 수 있는 장점이 있다.

자바는 JDK 1.8 이후 람다식을 도입하여 객체지향 프로그래밍과 함수형 프로그래밍을 혼합하여 사용할 수 있게 되었다.

 

람다식의 장단점

장점

  1. 코드를 간결하고 가독성 있게 작성할 수 있다.
  2. 메서드를 만드는 과정 없이 한 번에 처리가 가능하다.
  3. 병렬 처리가 용이하다.

 

단점

  1. 익명 함수의 재사용이 불가능하다.
  2. 재귀함수의 구현이 어렵다.
  3. 재사용이 불가능하므로 비슷한 함수 구현이 잦을 수 있다.
  4. 디버깅이 어렵다.

 

즉, 람다식은 무조건 옳은 것은 아니다. 상황에 맞추어 적절히 이용하는 것이 좋다.

 

람다식의 기본 문법

기본적으로 람다식은 반환 타입과 이름을 생략할 수 있다. 이를 익명 함수(Anonymous Function)라 부르기도 한다. 또한, 특정 조건이 충족되면 람다식은 더욱 축약하여 표현할 수 있다.

 

람다식의 메서드 표현 방식은 다음과 같다.

// 기존 표현 방식
void printHello() {
    System.out.println("Hello");
}
// 람다식
() -> System.out.println("Hello")

// 기존 표현 방식
int sum(int num1, int num2) {
    return num1 + num2;
}
// 람다식
(int num1, int num2) -> {
    num1 + num2
}
// 람다식의 또 다른 표현
(num1, num2) -> num1 + num2;

// 기존 방식
void printFive() {
    System.out.println(5);
}
// 람다식
() -> {System.out.println(5);}

// 기존 방식
int returnInt() {
    return 10;
}
// 람다식
() -> {return 10;}

// 기존 방식
void StrPrint(String str) {
    System.out.println(str);
}
// 람다식
(String str) -> {System.out.println(str);}

하지만, 위 코드들의 람다식을 자바에서 실제 코드로 제대로 활용 위해서는 함수형 인터페이스를 정의해야 한다.

 

함수형 인터페이스란?

자바에서 메서드는 반드시 클래스 안에서 정의해야 하며, 독립적으로 존재할 수 없다. 따라서, 클래스 객체를 생성한 후 객체를 통해 메서드를 호출해야 한다.

이와 비슷하게 람다식 또한 객체로 취급할 수 있으며, 이름이 없기 때문에 익명 클래스라고도 할 수 있다.

 

익명 클래스는 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 한 번만 사용되는 일회성 클래스를 말한다.

// sum 람다식
(num1, num2) -> num1 + num2;

// 람다식을 객체로 표현
new Object() {
    int sum(int num1, int num2) {
        return num1 + num2;
    }
}

또한, 람다를 객체로 생성할 지라도, 해당 객체에 접근하고 사용하기 위한 참조 변수가 필요하다.

하지만, Object 클래스에는 sum 메서드가 존재하지 않기 때문에, Object 타입의 참조 변수를 담더라도 해당 클래스를 이용할 수는 없다.

 

따라서, 아래 코드와 같이 객체를 생성하여 람다식을 구현하더라도 sum 메서드를 사용할 수 있는 방법이 없는 것이다.

public class LambdaTest {
    public static void main(String[] args) {
        Object obj = new Object() {
            int sum(int num1, int num2) {
                return num1 + num1;
            }
        };
        // Object obj = (num1, num2) -> num1 + num2;
    }
}

이러한 문제를 해결해주는 것이 바로 자바의 함수형 인터페이스(Functional Interface)이다.

즉, 자바에서 함수형 프로그래밍을 하기 위해 기존의 인터페이스 문법을 활용하여 람다식을 다룰 수 있도록 하는 것이다.

이는 람다식도 하나의 객체로 취급할 수 있기 때문에, 인터페이스에 정의된 추상 메서드를 구현하여 사용할 수 있다.

 

함수형 인터페이스는 단 하나의 추상 메서드만을 선언할 수 있다.

함수형 인터페이스는 함수형 프로그래밍을 하기 위해 만들어진 것으로 람다식은 재사용이 불가능한 익명 함수이다. 따라서 람다식과 함수형 인터페이스는 1:1로 매칭이 되어야 하기 때문이다.

 

다음은 함수형 인터페이스를 활용하여 람다식으로 sum 익명 함수를 구현하고 사용하는 예제이다.

@FunctionalInterface // 인터페이스가 바르게 정의되었는 지 확인하는 어노테이션
interface FunctionalEx {
    public abstract int sum(int num1, int num2);
}

public class LambdaTest {
    public static void main(String[] args) {
        FunctionalEx functionalEx = (num1, num2) -> num1 + num2;
        System.out.println(functionalEx.sum(10, 15));
    }
}

// 출력
// 25

결론적으로 자바에서 문법 요소를 해치지 않으면서 람다식을 활용하여 원하는 결과를 얻기 위해서는 함수형 인터페이스를 사용해야 한다.

함수형 인터페이스를 사용하면 참조변수의 타입으로 람다식에 접근이 가능하다.

 

매개변수와 리턴값이 없는 람다식

다음은 매개변수와 리턴 값이 없는 람다식을 구현하는 방법이다.

@FunctionalInterface
interface FunInterface {
    public void strPrint();
}

public class FunInterfaceEx {
    public static void main(String[] args) {
        FunInterface test;
        test = () -> {
            String str = "람다식 사용하기1";
            System.out.println(str);
        };
        test.strPrint();

        // 실행문이 하나일 경우 {} 생략 가능
        test = () -> System.out.println("람다식 사용하기2");
        test.strPrint();
    }
}

// 출력
// 람다식 사용하기1
// 람다식 사용하기2

 

매개변수가 있는 람다식

매개변수가 있는 람다식은 함수형 인터페이스에서 선언한 메서드의 매개변수의 개수만큼 람다식으로 이용할 수 있다.

@FunctionalInterface
interface FunInterface {
	public void intPrint(int num);
}

public class FunInterfaceEx {
    public static void main(String[] args) {
        FunInterface test;
        test = (num) -> {
            int result = num + 10;
            System.out.println(result);
        };
        test.intPrint(5);

        // 실행문이 하나일 경우 {} 생략 가능
        test = (num) -> System.out.println(num + 10);
        test.intPrint(5);
	}
}

// 출력
// 15
// 15

 

리턴값이 있는 람다식

@FunctionalInterface
interface FunInterface {
    public int sumEx(int num1, int num2);
}

public class FunInterfaceEx {
    public static void main(String[] args) {
        FunInterface test;
        test = (num1, num2) -> {
            int result = num1 + num2;
            return result;
        };
        int result1 = test.sumEx(5, 10);
        System.out.println(result1);
    
        test = (num1, num2) -> { return num1 + num2; };
        int result2 = test.sumEx(5, 10);
        System.out.println(result2);
    
        // 실행문이 한 줄인 경우 {}와 return 생략 가능
        test = (num1, num2) -> num1 + num2;
        int result3 = test.sumEx(5, 10);
        System.out.println(result3);
    
        // 메서드 활용
        test = (num1, num2) -> methodEx(num1, num2);
        int result4 = test.sumEx(5, 10);
        System.out.println(result4);
    }
  
    public static int methodEx(int a, int b) {
        return a + b;
    }
}

 

자바에서 기본적으로 제공하는 함수형 메서드

자바에서는 자주 사용되는 함수형 인터페이스를 기본적으로 제공하고 있다.

따라서 내장된 함수형 인터페이스를 사용하여, 익명 함수를 사용하는 데 많은 도움을 준다.

 

자바에서 내장된 함수형 인터페이스는 공식 문서에서 제공하고 있다.

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

따라서, 내장된 함수형 인터페이스를 활용하여 직접 함수형 인터페이스를 작성하지 않고도 원하는 기능을 람다식(익명 함수)으로 활용이 가능하다.

반응형