ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lambda(람다) 표현식 적용하기
    Java & Kotlin 2021. 6. 28. 20:09
    반응형

    1. Lambda Expression?

    오라클 공식 문서에서는 아래와 같이 설명하고 있습니다.

     

    "Lambda expression(이하 "람다식"이라 한다)은 메서드와 비슷합니다. 람다식은 알맞은 형식의 parameters의 목록과 이 parameters로 표현된 body를 제공합니다." 

     

    "람다식의 값은 functional interface(이하 "함수적 인터페이스"라 한다)의 인스턴스를 생성한다"라고 합니다.

     

    즉 람다식은 기본적으로 메서드처럼 파라미터를 받아, body를 구현합니다. 하지만 이는 함수적 인터페이스의 인스턴스를 생성하는 데 목적이 있습니다. 모든 메서드를 람다식으로 표현을 하는것이 아니라는 것을 알 수 있습니다.

     

    그렇다면 여기서 말하는 "함수적 인터페이스"는 뭘까요?

     

    2. Functional interface (함수적 인터페이스)

    간단히 아래와 같이 요약할 수 있습니다.

    • 오직 하나의 추상 메서드를 가지고 있는 인터페이스
    • 추상 메서드 - body가 구현되지 않은 메서드

     

    Calculator 인터페이스 (함수적 인터페이스 예제)

    @FunctionalInterface
    public interface Calculator { // 함수적 인터페이스
        int add(int a, int b); // 추상 메소드
    }
    • 추상 메서드가 하나인 함수적 인터페이스
    • @FunctionalInterface 애노테이션

     

    어차피 추상 메서드가 하나이면 함수적 인터페이스인데, 왜 애노테이션을 추가할까요?

     

    이는 람다식에 잘 못 참조된 인터페이스를 컴파일 단계에서 발견하기 위함입니다. 앞서 설명했듯이, 람다식은 오직 함수적 인터페이스의 인스턴스를 만드는데 집중하고 있습니다. 하지만 추후에 우연히 해당 인터페이스에 추상 메서드가 추가된다면 어떨까요? 이는 더 이상 함수적 인터페이스가 아니게 됩니다. 즉 람다식 사용할 수 없습니다. 

     

    이러한 에러를 방지하기 위해 @FunctionalInterface 애노테이션을 추가한다면, 컴파일 단계에서 해당 에러를 발견할 수 있습니다. 그래서 좀 더 안전하게 사용할 수 있습니다. 

     

    물론 애노테이션을 사용하지 않아도 됩니다. 애노테이션이 없더라도, 추상 메서드가 한 개일 때 함수적 인터페이스라는 사실은 변함이 없습니다.

     

    3. Lambda Expression이 필요한 이유

    Test 클래스

    여기서 test.addTest(new Calculator() { // 생략 }익명 클래스입니다. Calculator 인터페이스의 add() 메서드를 오버라이딩 하여 body를 재정의 하고 있습니다. 

     

    사실 결과적으로, 위의 코드는 아무런 문제가 없습니다. 잘 돌아가면 된 거 아닌가요? 왜 함수적 인터페이스, 람다가 필요한 걸까요?

     

    제가 생각하는 람다식의 가장 큰 장점은 코드가 간결해지는 것이라고 생각합니다. 코드가 간결해지는 것이 꼭 가독성이 좋다는 것을 의미하지는 않습니다. 하지만 불필요하게 반복되고, 길게 늘어진 코드를 효율적으로 줄일 수 있다면 장점이 된다고 생각합니다.

     

    Test 클래스 (기존)

     

    Calculator 인터페이스를 argument로 넣을 때 추상 메서드 오버라이딩으로 인해 길게 늘어진 것을 볼 수 있습니다. 고작 하나의 메서드 때문에 긴 코드를 작성하는 것은 가독성이 떨어진다고 생각합니다.

     

    이렇게 불필요하게 늘어진 코드를 짧고, 가독성 있게 바꿔보겠습니다. 

     

    Test 클래스 (람다식)

     

    • a = 1, b = 2 
    • Calculator.add(1, 2)
    • return: 3

     

    무려 6줄이던 코드가 1줄로 줄어들었습니다. 단순히 코드의 길이만 줄어든 게 아니라, 화살표 표현 방법으로 가독성도 살렸습니다.

     

    4. 실제 프로젝트 적용

    실제 스프링 프로젝트에서 적용된 코드를 리팩토링 하는 과정입니다. 

     

    기존

     

    기존 코드의 문제점 

    예외처리를 위해 if-else / Try-Catch 문의 사용은 간편합니다. 

     

    하지만 return 하나를 위해서 if-else문을 사용하는 것은 가독성이 떨어지고, 불필요하게 코드를 늘렸다고 생각합니다.

     

    리팩토링

     

    예외처리를 위한 else 구문을 orElseThrow() 메서드로 해결했습니다. 

     

    여기서 질문이 있을 수 있습니다. 람다식은 함수적 인터페이스에서만 사용이 가능한데, 여기서 함수적 인터페이스는 어디 있을까요?

     

    orElseThrow()는 파라미터로 Supplier 타입을 받습니다. 이때 Supplier는 함수적 인터페이스입니다. 즉 orElseThrow()는 함수적 인터페이스를 파라미터로 받기 위해서 "FindAnyFailException::new" 람다식이 사용됐습니다. 정확히는 람다식 중에서도 메서드 참조입니다. 

     

    4. 1. 메서드 참조란?

    람다식이 단 하나의 메서드만을 호출하는 경우에 매개변수를 제거하고 사용하는 것입니다. 

     

    5. 결론

    람다식은 익명 클래스가 너무 거추장스럽기 때문에, 함수로서 기능을 다른 메소드의 argument로 제공합니다. 앞선 예시에서처럼 argument 하나를 제공하기 위해서 익명 클래스처럼 길게 늘어진다면, 명확하지 않게 보입니다.

     

    람다식을 반드시 모든 코드에 사용해야 한다고 생각하지는 않습니다. 때로는 풀어서 명확하게 작성하는 것이 협업을 할 때 더 이해하기 쉽고, 의도를 파악하기 쉬울 때도 있다고 생각합니다. 

     

    하지만 그럼에도 불구하고 코드를 간결하게 작성할 수 있다는 점은 아주 매력적이라고 생각합니다. 혼자서 코드를 작성하거나, 람다식에 대한 충분한 합의가 된 상태에서 람다식을 사용하는 것은 좋은 방법이라고 생각합니다. 

     

    참고 문헌

    https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27

    http://wiki.plateer.com/pages/viewpage.action?pageId=7766426

    반응형

    댓글

Designed by Tistory.