혼자 강의를 보면서 개발을 하다보면 다음과 같은 코드를 자주 볼 수 있다. 이 코드들이 람다식과 관련있다는 것은 알았지만 정확히 어떤 원리로 코드가 작성되고 동작하는지 알 수 없었다.
userRepository.findById(Id).orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
그래서 람다 표현식에 대해 자세히 알아보기 위해서 위 코드들이 어떤 원리로 작성되었는지 살펴보며 람다의 개념을 이해해보도록 하겠다.
결론부터 말하자면 람다는 함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다. 위의 코드에서는 사진과 같이 Supplier이라는 함수형 인터페이스에 정의 되어 있는 get()이라는 문맥을 람다 표현식으로 구현하여 위와 같이 코드를 작성한 것이다.
그럼 어떻게 이렇게 Supplier의 get()함수를 저렇게 간단하게 람다식으로 사용할 수 있었을까? 또 함수형 인터페이스는 무엇일까?
먼저 함수형 인터페이스를 살펴보며, Supplier과 같이 정확히 하나의 추상 메서드를 지정하는 인터페이스를 함수형 인터페이스라고 한다.
@FunctionalInterface는 함수형 인터페이스를 가리키는 어노테이션이다. 따라서 함수형 인터페이스가 아닌 인터페이스에 해당 어노테이션을 붙인다면 컴파일 에러가 발생한다.
자바 API의 함수형 인터페이스로는 Comparator, Runnable 등이 있다. 또 함수형 인터페이스는 개발자 들이 하나의 추상 메서드로 작성한다면 필요한 함수형 인터페이스들은 만들어 사용할 수 있다.
userRepository.findById(id).orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
이 코드는 람다가 없었다면 원래 다음과 같이 supplier의 인스턴스를 만들어 메서드를 구현하고 .orElseThrow()에 supplier의 인스턴스를 파라미터로 전달하고 이전의 사진에서처럼 orElseThrow() 내부에서 get()를 실행시킨다.
//인스턴스 생성, get()구현
Supplier<CustomException> supplier = new Supplier<CustomException>() {
@Override
public CustomException get() {
return new CustomException(ErrorCode.POST_NOT_FOUND);
}
};
//supplier 인스턴스를 파라미터로 전달
return userRepository.findById(id).orElseThrow(supplier);
람다 표현식이 생기기 이전엔 어떻게 작성하는지 알았으니 다시 단계별로 람다식으로 만들며 작성원리를 이해해야한다. 먼저 함수형 인터페이스를 람다로 어떻게 표현하며 람다란 무엇인지 알아보자.
람다 표현식은 다음과 같이 이름은 없고 파라미터 리스트, 바디, 반환형식, 발생할 수 있는 예외 리스트를 가질 수 있는 구조로 되어있다.
위 코드는 Comparator함수형 인터페이스의 compare()메서드를 람다로 표현한 코드이다.
compare메서드를 보면 2개의 파라미터가 들어가고 리턴 타입이 int인 것을 확인할 수 있다. 이럴 경우 위의 코드와 같이 함수 이름은 사용하지 않고 compare에 맞는 파라미터와 리턴 타입에 맞는 결과가 나오도록 람다식을 작성한다. 참고로 람다 표현식에는 return이 함축되어 있어 return를 명시적으로 사용하지 않아도 된다.
함수형 인터페이스에 제넥릭이 많이 사용되고 있다. 내용이 너무 많아져 따로 설명하지 않으니 이 블로그를 참고하세요.
그럼 우리가 처음 봤던 코드를 람다식으로 표현해보자.
먼저 Supplier의 get()메서드는 다음과 같이 람다식으로 표현하고 사용할 수 있다.
Supplier<CustomException> supplier = () -> {throw new CustomException(ErrorCode.POST_NOT_FOUND);};
return userRepository.findById(id).orElseThrow(supplier);
Supplier를 인스턴스를 만들 때 Supplier은 단 하나의 추상 메서드를 가지기 때문에 T get() (T -> CustomException) 를 익명성을 가지는 람다식으로 위와 같이 표현할 수 있다.
그리고 이렇게 동작을 정의한 인스턴스를 파라미터로 넘겨주는 것을 동작 파라미터화라고 한다. 이렇게 동작을 정의해 파라미터로 넘겨주면 요구사항에 효과적으로 대응할 수 있다는 장점이 있다. 나중에 시간이 된다면 동작 파라미터에 대한 개념을 잘 이해하고 넘어가면 좋을 거 같다.
위의 코드를 더 간결하게 작성한다면 굳이 supplier이라는 인스턴그를 만들지 않고 orElseThrow안에 직접 파라미터를 적으면 우리가 처음에 봤던 다음과 같은 코다가 완성이 된다.
userRepository.findById(Id).orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
이 블로그는 '모던 자바 인 액션' 이라는 책을 참고하여 작성했다. 책의 내용을 이해하고 정말 많은 사람에게 도움이 될 수 있겠다는 생각을 가지면 블로그 작성을 시작했다. 책에 정말 좋은 내용이 많았고 이 이해한 만큼 블로그에 녹이고 싶었지만 생각보다 많은 내용을 담지 못해서 아쉬웠다. 이 역시 나의 기초 지식이 부족하기에 많은 내용을 담기 어렸웠던 것 같고 앞으로도 더 많이 노력해야함을 느꼈다.
참고: 모던 자바 인 액션(한빛미디어)